diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 00000000000..ee91f5d33e2
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,184 @@
+# git blame ignore revs file
+#
+# The list of revisions below will be ignored by git-blame (1) if this file gets
+# passed via the --ignore-revs-file command line option or is configured using
+# the blame.ignoreRevsFile key.
+#
+# Only add commits that obviously do not change semantics, e.g. mechanical refactorings
+# or formatting. Always add the commit message as a comment above the revision
+# to keep the file readable.
+
+# 8299973: Replace NULL with nullptr in share/utilities/
+1084fd24eb118d4131538c2a3ead714db7d0357b
+
+# 8299974: Replace NULL with nullptr in share/adlc/
+62537d200f01d58ff1c236f31f71c5839316db9e
+
+# 8300081: Replace NULL with nullptr in share/asm/
+9d5bab11f08a992803399f422d75b17f8607df72
+
+# 8300086: Replace NULL with nullptr in share/c1/
+90d5041b6a055d6266140ffea2aa9a3b08b32209
+
+# 8300087: Replace NULL with nullptr in share/cds/
+eca64795be63c599a637ce2a7f740b2d0a1ec9bc
+
+# 8300222: Replace NULL with nullptr in share/logging
+bd5ca953058704087da4bc5796b3ce28ce2a8f78
+
+# 8300240: Replace NULL with nullptr in share/ci/
+f52d35c84b7333809156d201c866793854143888
+
+# 8300241: Replace NULL with nullptr in share/classfile/
+49ff52087be8b95cbf369518281312ecc9d83618
+
+# 8300242: Replace NULL with nullptr in share/code/
+cfe57466ddecb93b528478d0b053b089dd1ed285
+
+# 8300243: Replace NULL with nullptr in share/compiler/
+fcbf9d052efd16821750fb20813f8030ee828472
+
+# 8300244: Replace NULL with nullptr in share/interpreter/
+a5d8e12872d9de399fa97b33896635d101b71372
+
+# 8300245: Replace NULL with nullptr in share/jfr/
+cc396895e5a1dac49f4e341ce91c04b8c092d0af
+
+# 8300651: Replace NULL with nullptr in share/runtime/
+71107f4648d8f31a7bcc0aa5202ef46230df583f
+
+# 8301068: Replace NULL with nullptr in share/jvmci/
+90ec19efeda90f13a918b4481fe6ee552ab2af66
+
+# 8301069: Replace NULL with nullptr in share/libadt/
+b0376a5f4421fb58c0feeddfce2c2083314e400c
+
+# 8301070: Replace NULL with nullptr in share/memory/
+d98a323a8b972c17a066c597a81b164681ad5589
+
+# 8301072: Replace NULL with nullptr in share/oops/
+c8ace482edead720c865cf996729a316025d937e
+
+# 8301074: Replace NULL with nullptr in share/opto/
+5726d31e56530bbe7dee61ae04b126e20cb3611d
+
+# 8301076: Replace NULL with nullptr in share/prims/
+b76a52f2104b63e84e5d09f47ce01dd0cb3935d7
+
+# 8301077: Replace NULL with nullptr in share/services/
+5c1ec82656323872c4628026662fe5b62e7a61e3
+
+# 8301178: Replace NULL with nullptr in share/gc/epsilon/
+b77abc6a0daed0e01a9003d42493320376dc98bc
+
+# 8301179: Replace NULL with nullptr in share/gc/serial/
+107e184d59c0bbed6441a3c1a9bfd4527da3bce5
+
+# 8301180: Replace NULL with nullptr in share/gc/parallel/
+3758487fda61b27e7e684413793ed28c0b9e64d3
+
+# 8301223: Replace NULL with nullptr in share/gc/g1/
+75a4edca6b9fa6b3e66b564aeb4d7ca8acf02491
+
+# 8301225: Replace NULL with nullptr in share/gc/shenandoah/
+0c9658446d111ec944f06b7a8a4e3ae7bf53ee8d
+
+# 8301477: Replace NULL with nullptr in os/aix
+43288bbd684abfcefdf385ed1e0307070399ccbf
+
+# 8301478: Replace NULL with nullptr in os/bsd
+716f1df609e7f0aa7b3b9383d23dde5c71017d02
+
+# 8301479: Replace NULL with nullptr in os/linux
+ac9e046748a9bb6ee065dc473d82135ce36043b7
+
+# 8301480: Replace NULL with nullptr in os/posix
+4539899c55c77771b951d005c17550ef9ac94819
+
+# 8301481: Replace NULL with nullptr in os/windows
+c91cd2814baa8dee2af8af0fecf9185d4a0a44cf
+
+# 8301493: Replace NULL with nullptr in cpu/aarch64
+948f3b3c24709eca3aa6c3f0db6adb9226d6f9ac
+
+# 8301494: Replace NULL with nullptr in cpu/arm
+c4ffe4bf6369d5b271aa8689b8648f3fe8dcabed
+
+# 8301495: Replace NULL with nullptr in cpu/ppc
+0826ceee65ab83f643a77716f8f12d0060369923
+
+# 8301496: Replace NULL with nullptr in cpu/riscv
+d2ce04bb101002abfdb7c8adb3fa8ea267903c36
+
+# 8301497: Replace NULL with nullptr in cpu/s390
+54f7b6ca34986cc26c5b91c6724b9a1754c94391
+
+# 8301498: Replace NULL with nullptr in cpu/x86
+4154a980ca28c1ae56db26e3dce64c07c225de12
+
+# 8301499: Replace NULL with nullptr in cpu/zero
+4e327db1d127c652ef39e31c164e36ae429a0065
+
+# 8301500: Replace NULL with nullptr in os_cpu/aix_ppc
+c8307e37fdf4453cade84efc113d93dd14333fd0
+
+# 8301501: Replace NULL with nullptr in os_cpu/bsd_aarch64
+218223e4a31d485935655cb3f186a752defd8fa8
+
+# 8301502: Replace NULL with nullptr in os_cpu/bsd_x86
+6daff6b26946748360d59a12e9069a08ab5ca06d
+
+# 8301503: Replace NULL with nullptr in os_cpu/bsd_zero
+8cc399b672c6ce08037685b3a3a2db3c53a87b50
+
+# 8301504: Replace NULL with nullptr in os_cpu/linux_aarch64
+13fcd602d37eb0095f169255128588b872639571
+
+# 8301505: Replace NULL with nullptr in os_cpu/linux_arm
+b81f0ff43ac8d1431f2f5dccb7499a3a1503823d
+
+# 8301506: Replace NULL with nullptr in os_cpu/linux_ppc
+b1e96989b693aadea082a01576e25f85ed28ff0d
+
+# 8301507: Replace NULL with nullptr in os_cpu/linux_riscv
+182d1b2fb7034b6e9177dc360cbea43d548c3ff0
+
+# 8301508: Replace NULL with nullptr in os_cpu/linux_s390
+d097b5e6285e1a59632211e006592fedf2047c09
+
+# 8301509: Replace NULL with nullptr in os_cpu/linux_x86
+5d1f71daf06870810c9ca24e911d6191cc4f3006
+
+# 8301511: Replace NULL with nullptr in os_cpu/linux_zero
+42a286a15862d9a05ea3477a9eeab46e7b33e599
+
+# 8301512: Replace NULL with nullptr in os_cpu/windows_aarch64
+ad79e49141f063a61090eda69d96dc580db88949
+
+# 8301513: Replace NULL with nullptr in os_cpu/windows_x86
+c109dae48c61c6fbeacadf59d509d37d2c4d2bb8
+
+# 8308092: Replace NULL with nullptr in gc/x
+599fa774b875da971d66f79e5e43ede2b5ce18aa
+
+# 8309044: Replace NULL with nullptr, final sweep of hotspot code
+4f16161607edbf69f423ced1d3c24f7af058d46b
+
+# 8324286: Fix backsliding on use of nullptr instead of NULL
+bcb340da091e3287da8d2ecfcd017ebcc6613cae
+
+# 8324678: Replace NULL with nullptr in HotSpot gtests
+c1281e6b45ed167df69d29a6039d81854c145ae6
+
+# 8324679: Replace NULL with nullptr in HotSpot .ad files
+b3ecd55601d483359819d02e70789bbd412b13da
+
+# 8324680: Replace NULL with nullptr in JVMTI generated code
+267780bf0adf4bfd831fbc04347e297fa8f3bb01
+
+# 8324681: Replace NULL with nullptr in HotSpot jtreg test native code files
+a6bdee48f39993128d8095d40ab417f0102af0f4
+
+# 8324799: Use correct extension for C++ test headers
+998d0baab0fd051c38d9fd6021628eb863b80554
+
diff --git a/.jcheck/conf b/.jcheck/conf
index 25af49f8ef8..f1ac0f37a06 100644
--- a/.jcheck/conf
+++ b/.jcheck/conf
@@ -1,7 +1,7 @@
[general]
project=jdk
jbs=JDK
-version=27
+version=28
[checks]
error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists,copyright
diff --git a/doc/building.html b/doc/building.html
index 86ee3390ead..be3c8c364d7 100644
--- a/doc/building.html
+++ b/doc/building.html
@@ -394,11 +394,11 @@ to date at the time of writing.
Linux/x64
-
Oracle Enterprise Linux 6.4 / 8.x
+
Oracle Linux 6.4 / 8.x
Linux/aarch64
-
Oracle Enterprise Linux 7.6 / 8.x
+
Oracle Linux 7.6 / 8.x
macOS
@@ -1495,26 +1495,24 @@ following targets are known to work:
-
BASE_OS must be one of OL for Oracle
-Enterprise Linux or Fedora. If the base OS is
-Fedora the corresponding Fedora release can be specified
-with the help of the BASE_OS_VERSION option. If the build
-is successful, the new devkits can be found in the
+
BASE_OS must be one of OL for Oracle Linux
+or Fedora. The release/version of the base OS can be
+specified using the BASE_OS_VERSION option. If the build is
+successful, the new devkits can be found in the
build/devkit/result subdirectory:
cd make/devkit
-make TARGETS="ppc64le-linux-gnu aarch64-linux-gnu" BASE_OS=Fedora BASE_OS_VERSION=21
+make TARGETS="ppc64le-linux-gnu aarch64-linux-gnu" BASE_OS=Fedora
ls -1 ../../build/devkit/result/
x86_64-linux-gnu-to-aarch64-linux-gnu
x86_64-linux-gnu-to-ppc64le-linux-gnu
Notice that devkits are not only useful for targeting different build
platforms. Because they contain the full build dependencies for a system
-(i.e. compiler and root file system), they can easily be used to build
-well-known, reliable and reproducible build environments. You can for
-example create and use a devkit with GCC 7.3 and a Fedora 12 sysroot
-environment (with glibc 2.11) on Ubuntu 14.04 (which doesn't have GCC
-7.3 by default) to produce JDK binaries which will run on all Linux
-systems with runtime libraries newer than the ones from Fedora 12 (e.g.
-Ubuntu 16.04, SLES 11 or RHEL 6).
+(i.e., compiler and root file system/sysroot), they can easily be used
+to build well-known, reliable, and reproducible build environments. You
+can, for example, create and use a devkit with a version of the GCC
+compiler not provided by the host OS, using a sysroot from an older
+Linux distribution to produce JDK binaries which will run on all Linux
+systems with newer runtime libraries.
Using Debian debootstrap
On Debian (or a derivative like Ubuntu), you can create sysroots for
foreign architectures with tools provided by the OS. You can use
diff --git a/doc/building.md b/doc/building.md
index 93ab386ee8e..adf116764fa 100644
--- a/doc/building.md
+++ b/doc/building.md
@@ -195,8 +195,8 @@ time of writing.
| Operating system | Vendor/version used |
| ----------------- | ---------------------------------- |
-| Linux/x64 | Oracle Enterprise Linux 6.4 / 8.x |
-| Linux/aarch64 | Oracle Enterprise Linux 7.6 / 8.x |
+| Linux/x64 | Oracle Linux 6.4 / 8.x |
+| Linux/aarch64 | Oracle Linux 7.6 / 8.x |
| macOS | macOS 14.x |
| Windows | Windows Server 2016 |
@@ -1288,27 +1288,26 @@ at least the following targets are known to work:
| riscv64-linux-gnu |
| s390x-linux-gnu |
-`BASE_OS` must be one of `OL` for Oracle Enterprise Linux or `Fedora`. If the
-base OS is `Fedora` the corresponding Fedora release can be specified with the
-help of the `BASE_OS_VERSION` option. If the build is successful, the new
-devkits can be found in the `build/devkit/result` subdirectory:
+`BASE_OS` must be one of `OL` for Oracle Linux or `Fedora`. The release/version
+of the base OS can be specified using the `BASE_OS_VERSION` option. If the build
+is successful, the new devkits can be found in the `build/devkit/result`
+subdirectory:
```
cd make/devkit
-make TARGETS="ppc64le-linux-gnu aarch64-linux-gnu" BASE_OS=Fedora BASE_OS_VERSION=21
+make TARGETS="ppc64le-linux-gnu aarch64-linux-gnu" BASE_OS=Fedora
ls -1 ../../build/devkit/result/
x86_64-linux-gnu-to-aarch64-linux-gnu
x86_64-linux-gnu-to-ppc64le-linux-gnu
```
Notice that devkits are not only useful for targeting different build
-platforms. Because they contain the full build dependencies for a system (i.e.
-compiler and root file system), they can easily be used to build well-known,
-reliable and reproducible build environments. You can for example create and
-use a devkit with GCC 7.3 and a Fedora 12 sysroot environment (with glibc 2.11)
-on Ubuntu 14.04 (which doesn't have GCC 7.3 by default) to produce JDK binaries
-which will run on all Linux systems with runtime libraries newer than the ones
-from Fedora 12 (e.g. Ubuntu 16.04, SLES 11 or RHEL 6).
+platforms. Because they contain the full build dependencies for a system (i.e.,
+compiler and root file system/sysroot), they can easily be used to build
+well-known, reliable, and reproducible build environments. You can, for example,
+create and use a devkit with a version of the GCC compiler not provided by the
+host OS, using a sysroot from an older Linux distribution to produce JDK
+binaries which will run on all Linux systems with newer runtime libraries.
#### Using Debian debootstrap
diff --git a/make/CompileDemos.gmk b/make/CompileDemos.gmk
index 503edf18e00..ab3545facec 100644
--- a/make/CompileDemos.gmk
+++ b/make/CompileDemos.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@@ -173,41 +173,41 @@ $(BUILD_DEMO_CodePointIM_JAR): $(CODEPOINT_METAINF_SERVICE_FILE)
$(eval $(call SetupBuildDemo, FileChooserDemo, \
DEMO_SUBDIR := jfc, \
- DISABLED_WARNINGS := rawtypes deprecation unchecked this-escape, \
+ DISABLED_WARNINGS := this-escape, \
))
$(eval $(call SetupBuildDemo, SwingSet2, \
DEMO_SUBDIR := jfc, \
EXTRA_COPY_TO_JAR := .java, \
EXTRA_MANIFEST_ATTR := SplashScreen-Image: resources/images/splash.png, \
- DISABLED_WARNINGS := rawtypes deprecation unchecked static serial cast this-escape, \
+ DISABLED_WARNINGS := rawtypes static serial cast this-escape, \
))
$(eval $(call SetupBuildDemo, Font2DTest, \
- DISABLED_WARNINGS := rawtypes deprecation unchecked serial cast this-escape dangling-doc-comments, \
+ DISABLED_WARNINGS := serial dangling-doc-comments, \
DEMO_SUBDIR := jfc, \
))
$(eval $(call SetupBuildDemo, J2Ddemo, \
DEMO_SUBDIR := jfc, \
MAIN_CLASS := java2d.J2Ddemo, \
- DISABLED_WARNINGS := rawtypes deprecation unchecked cast lossy-conversions this-escape, \
+ DISABLED_WARNINGS := cast lossy-conversions this-escape, \
JAR_NAME := J2Ddemo, \
))
$(eval $(call SetupBuildDemo, Metalworks, \
- DISABLED_WARNINGS := rawtypes unchecked this-escape, \
+ DISABLED_WARNINGS := this-escape, \
DEMO_SUBDIR := jfc, \
))
$(eval $(call SetupBuildDemo, Notepad, \
- DISABLED_WARNINGS := rawtypes this-escape, \
+ DISABLED_WARNINGS := this-escape, \
DEMO_SUBDIR := jfc, \
))
$(eval $(call SetupBuildDemo, Stylepad, \
DEMO_SUBDIR := jfc, \
- DISABLED_WARNINGS := rawtypes unchecked this-escape, \
+ DISABLED_WARNINGS := this-escape, \
EXTRA_SRC_DIR := $(DEMO_SHARE_SRC)/jfc/Notepad, \
EXCLUDE_FILES := $(DEMO_SHARE_SRC)/jfc/Notepad/README.txt, \
))
@@ -217,7 +217,7 @@ $(eval $(call SetupBuildDemo, SampleTree, \
))
$(eval $(call SetupBuildDemo, TableExample, \
- DISABLED_WARNINGS := rawtypes unchecked deprecation this-escape dangling-doc-comments, \
+ DISABLED_WARNINGS := dangling-doc-comments, \
DEMO_SUBDIR := jfc, \
))
diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js
index 4c1d2835054..20315cda97d 100644
--- a/make/conf/jib-profiles.js
+++ b/make/conf/jib-profiles.js
@@ -1192,8 +1192,8 @@ var getJibProfilesDependencies = function (input, common) {
server: "jpg",
product: "jcov",
version: "3.0",
- build_number: "5",
- file: "bundles/jcov-3.0+5.zip",
+ build_number: "6",
+ file: "bundles/jcov-3.0+6.zip",
environment_name: "JCOV_HOME",
},
diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf
index 4f63179ae05..83cfa86cf70 100644
--- a/make/conf/version-numbers.conf
+++ b/make/conf/version-numbers.conf
@@ -26,17 +26,17 @@
# Default version, product, and vendor information to use,
# unless overridden by configure
-DEFAULT_VERSION_FEATURE=27
+DEFAULT_VERSION_FEATURE=28
DEFAULT_VERSION_INTERIM=0
DEFAULT_VERSION_UPDATE=0
DEFAULT_VERSION_PATCH=0
DEFAULT_VERSION_EXTRA1=0
DEFAULT_VERSION_EXTRA2=0
DEFAULT_VERSION_EXTRA3=0
-DEFAULT_VERSION_DATE=2026-09-15
-DEFAULT_VERSION_CLASSFILE_MAJOR=71 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`"
+DEFAULT_VERSION_DATE=2027-03-23
+DEFAULT_VERSION_CLASSFILE_MAJOR=72 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`"
DEFAULT_VERSION_CLASSFILE_MINOR=0
DEFAULT_VERSION_DOCS_API_SINCE=11
-DEFAULT_ACCEPTABLE_BOOT_VERSIONS="26 27"
-DEFAULT_JDK_SOURCE_TARGET_VERSION=27
+DEFAULT_ACCEPTABLE_BOOT_VERSIONS="26 27 28"
+DEFAULT_JDK_SOURCE_TARGET_VERSION=28
DEFAULT_PROMOTED_VERSION_PRE=ea
diff --git a/make/devkit/Common.gmk b/make/devkit/Common.gmk
new file mode 100644
index 00000000000..f9c42350103
--- /dev/null
+++ b/make/devkit/Common.gmk
@@ -0,0 +1,107 @@
+#
+# Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+ARCH := $(word 1,$(subst -, ,$(TARGET)))
+
+ifeq ($(TARGET), arm-linux-gnueabihf)
+ ARCH=armhfp
+endif
+
+$(info ARCH=$(ARCH))
+
+ifeq ($(PREFIX),)
+ $(error PREFIX not set)
+endif
+
+BUILDDIR := $(OUTPUT_ROOT)/$(HOST)/$(TARGET)
+TARGETDIR := $(PREFIX)/$(TARGET)
+SYSROOT := $(TARGETDIR)/sysroot
+
+# Base OS information and repositories
+#
+# BASE_OS_REPOS is a space-separated set of repositories. Each entry
+# is of the format , where is a unique
+# identifier across the set of repositories.
+
+ifeq ($(BASE_OS), OL)
+ ifeq ($(filter aarch64 x86_64, $(ARCH)), )
+ $(error Only "aarch64 x86_64" architectures are supported for OL, but "$(ARCH)" was requested)
+ endif
+ BASE_OS_VERSION ?= 7
+ ifeq ($(BASE_OS_VERSION), 6)
+ ifeq ($(filter x86_64, $(ARCH)), )
+ $(error Only "x86_64" architectures are supported for OL6, but "$(ARCH)" was requested)
+ endif
+ BASE_OS_DESCRIPTION_VERSION := 6.4
+ REPO_BASE_URL := https://yum.oracle.com/repo/OracleLinux/OL6/4
+ BASE_OS_REPOS := base,$(REPO_BASE_URL)/base/$(ARCH)
+ else ifeq ($(BASE_OS_VERSION), 7)
+ BASE_OS_DESCRIPTION_VERSION := 7.6
+ REPO_BASE_URL := https://yum.oracle.com/repo/OracleLinux/OL7/6
+ BASE_OS_REPOS := base,$(REPO_BASE_URL)/base/$(ARCH)
+ else
+ REPO_BASE_URL := https://yum.oracle.com/repo/OracleLinux/OL$(BASE_OS_VERSION)
+ BASE_OS_REPOS := baseos,$(REPO_BASE_URL)/baseos/latest/$(ARCH) appstream,$(REPO_BASE_URL)/appstream/$(ARCH)
+ BASE_OS_DESCRIPTION_VERSION := $(BASE_OS_VERSION)
+ endif
+ BASE_OS_DESCRIPTION := OL$(BASE_OS_DESCRIPTION_VERSION)
+else ifeq ($(BASE_OS), Fedora)
+ ifeq ($(filter aarch64 armhfp ppc64le riscv64 s390x x86_64, $(ARCH)), )
+ $(error Only "aarch64 armhfp ppc64le riscv64 s390x x86_64" architectures are supported for Fedora, but "$(ARCH)" was requested)
+ endif
+ ifeq ($(ARCH), riscv64)
+ BASE_OS_VERSION ?= 43
+ BASE_OS_BUILD := 6640
+ REPO_BASE_URL := https://riscv-koji.fedoraproject.org/repos-dist/f$(BASE_OS_VERSION)/$(BASE_OS_BUILD)/riscv64
+ BASE_OS_REPOS := riscv64,$(REPO_BASE_URL)
+ else
+ ifeq ($(ARCH), armhfp)
+ BASE_OS_VERSION ?= 36
+ else
+ BASE_OS_VERSION ?= 41
+ endif
+ ifeq ($(ARCH), armhfp)
+ ifneq ($(BASE_OS_VERSION), 36)
+ $(error Fedora 36 is the last release supporting "armhfp", but $(BASE_OS_VERSION) was requested)
+ endif
+ endif
+ LATEST_ARCHIVED_OS_VERSION := 41
+ ifeq ($(filter aarch64 x86_64 armhfp, $(ARCH)), )
+ FEDORA_TYPE := fedora-secondary
+ else
+ FEDORA_TYPE := fedora/linux
+ endif
+ NOT_ARCHIVED := $(shell [ $(BASE_OS_VERSION) -gt $(LATEST_ARCHIVED_OS_VERSION) ] && echo true)
+ ifeq ($(NOT_ARCHIVED),true)
+ RPM_REPO_URL := https://dl.fedoraproject.org/pub/$(FEDORA_TYPE)/releases/$(BASE_OS_VERSION)/Everything/$(ARCH)/os
+ else
+ RPM_REPO_URL := https://archives.fedoraproject.org/pub/archive/$(FEDORA_TYPE)/releases/$(BASE_OS_VERSION)/Everything/$(ARCH)/os
+ endif
+ BASE_OS_REPOS := os,$(RPM_REPO_URL)
+ endif
+ BASE_OS_DESCRIPTION := Fedora_$(BASE_OS_VERSION)
+else
+ $(error Unknown base OS $(BASE_OS))
+endif
diff --git a/make/devkit/Makefile b/make/devkit/Makefile
index 30e0dce0839..41ebb8b980c 100644
--- a/make/devkit/Makefile
+++ b/make/devkit/Makefile
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@@ -39,7 +39,7 @@
#
# make TARGETS="aarch64-linux-gnu" BASE_OS=Fedora
# or
-# make TARGETS="arm-linux-gnueabihf ppc64le-linux-gnu" BASE_OS=Fedora BASE_OS_VERSION=17
+# make TARGETS="aarch64-linux-gnu ppc64le-linux-gnu" BASE_OS=Fedora BASE_OS_VERSION=41
#
# to build several devkits for a specific OS version at once.
# You can find the final results under ../../build/devkit/result/-to-
@@ -50,7 +50,7 @@
# makefile again for cross compilation. Ex:
#
# PATH=$PWD/../../build/devkit/result/x86_64-linux-gnu-to-x86_64-linux-gnu/bin:$PATH \
-# make TARGETS="arm-linux-gnueabihf ppc64le-linux-gnu" BASE_OS=Fedora
+# make TARGETS="aarch64-linux-gnu ppc64le-linux-gnu" BASE_OS=Fedora
#
# This is the makefile which iterates over all host and target platforms.
#
@@ -79,10 +79,10 @@ TARGET_PLATFORMS := $(PLATFORMS)
$(info HOST_PLATFORMS $(HOST_PLATFORMS))
$(info TARGET_PLATFORMS $(TARGET_PLATFORMS))
-all compile : $(PLATFORMS)
+all compile: $(PLATFORMS)
ifeq ($(SKIP_ME), )
- $(foreach p,$(filter-out $(ME),$(PLATFORMS)),$(eval $(p) : $$(ME)))
+ $(foreach p,$(filter-out $(ME),$(PLATFORMS)),$(eval $(p): $$(ME)))
endif
OUTPUT_ROOT = $(abspath ../../build/devkit)
@@ -90,38 +90,39 @@ RESULT = $(OUTPUT_ROOT)/result
SUBMAKEVARS = HOST=$@ BUILD=$(ME) RESULT=$(RESULT) OUTPUT_ROOT=$(OUTPUT_ROOT)
-$(HOST_PLATFORMS) :
+$(HOST_PLATFORMS):
@echo 'Building compilers for $@'
@echo 'Targets: $(TARGET_PLATFORMS)'
for p in $(filter $@, $(TARGET_PLATFORMS)) $(filter-out $@, $(TARGET_PLATFORMS)); do \
- $(MAKE) -f Tools.gmk download-rpms $(SUBMAKEVARS) \
+ $(MAKE) -f Sysroot.gmk sysroot $(SUBMAKEVARS) \
TARGET=$$p PREFIX=$(RESULT)/$@-to-$$p && \
$(MAKE) -f Tools.gmk all $(SUBMAKEVARS) \
TARGET=$$p PREFIX=$(RESULT)/$@-to-$$p && \
$(MAKE) -f Tools.gmk ccache $(SUBMAKEVARS) \
- TARGET=$@ PREFIX=$(RESULT)/$@-to-$$p || exit 1 ; \
+ TARGET=$@ PREFIX=$(RESULT)/$@-to-$$p || exit 1; \
done
- @echo 'All done"'
+ @echo 'All done'
TODAY := $(shell date +%Y%m%d)
define Mktar
$(1)-to-$(2)_tar = $$(RESULT)/sdk-$(1)-to-$(2)-$$(TODAY).tar.gz
- $$($(1)-to-$(2)_tar) : PLATFORM = $(1)-to-$(2)
+ $$($(1)-to-$(2)_tar): PLATFORM = $(1)-to-$(2)
TARFILES += $$($(1)-to-$(2)_tar)
endef
$(foreach p,$(HOST_PLATFORMS),$(foreach t,$(TARGET_PLATFORMS),$(eval $(call Mktar,$(p),$(t)))))
-tars : all $(TARFILES)
-onlytars : $(TARFILES)
-%.tar.gz :
+tars: all $(TARFILES)
+onlytars: $(TARFILES)
+
+%.tar.gz:
$(MAKE) -r -f Tars.gmk SRC_DIR=$(RESULT)/$(PLATFORM) TAR_FILE=$@
-clean :
+clean:
rm -rf $(addprefix ../../build/devkit/, result $(HOST_PLATFORMS))
+
dist-clean: clean
rm -rf $(addprefix ../../build/devkit/, src download)
-FORCE :
-.PHONY : all compile tars $(HOST_PLATFORMS) clean dist-clean
+.PHONY: all compile tars $(HOST_PLATFORMS) clean dist-clean
diff --git a/make/devkit/Sysroot.gmk b/make/devkit/Sysroot.gmk
new file mode 100644
index 00000000000..13395172074
--- /dev/null
+++ b/make/devkit/Sysroot.gmk
@@ -0,0 +1,208 @@
+#
+# Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+include Common.gmk
+
+$(info TARGET=$(TARGET))
+$(info HOST=$(HOST))
+$(info BUILD=$(BUILD))
+
+ifeq ($(BASE_OS)-$(BASE_OS_VERSION)-$(ARCH), OL-7-aarch64)
+ KERNEL_HEADERS_RPM := kernel-uek-headers
+else
+ KERNEL_HEADERS_RPM := kernel-headers
+endif
+
+ifneq ($(BASE_OS)-$(BASE_OS_VERSION), OL-6)
+ WAYLAND_RPMS := wayland-devel wayland-protocols-devel
+endif
+
+ZLIB_RPMS := zlib zlib-devel
+ifeq ($(BASE_OS), Fedora)
+ ifneq ($(ARCH), armhfp)
+ ZLIB_RPMS := zlib-ng zlib-ng-devel
+ endif
+endif
+
+
+################################################################################
+
+# RPMs to include
+RPM_LIST := \
+ filesystem \
+ $(KERNEL_HEADERS_RPM) \
+ glibc glibc-devel \
+ cups-libs cups-devel \
+ libX11 libX11-devel \
+ libxcb xorg-x11-proto-devel \
+ alsa-lib alsa-lib-devel \
+ libXext libXext-devel \
+ libXtst libXtst-devel \
+ libXrender libXrender-devel \
+ libXrandr libXrandr-devel \
+ freetype freetype-devel \
+ libXt libXt-devel \
+ libSM libSM-devel \
+ libICE libICE-devel \
+ libXi libXi-devel \
+ libXau libXau-devel \
+ libgcc \
+ $(ZLIB_RPMS) \
+ libffi libffi-devel \
+ fontconfig fontconfig-devel \
+ systemtap-sdt-devel \
+ $(WAYLAND_RPMS) \
+ #
+
+################################################################################
+# Define common directories and files
+
+DOWNLOAD := $(OUTPUT_ROOT)/download
+DOWNLOAD_RPMS := $(DOWNLOAD)/rpms/$(TARGET)-$(BASE_OS)-$(BASE_OS_VERSION)
+SRCDIR := $(OUTPUT_ROOT)/src
+
+################################################################################
+# Marker files
+
+DOWNLOAD_RPMS_MARKER := $(BUILDDIR)/download-rpms.marker
+RPMS_UNPACKED_MARKER := $(BUILDDIR)/rpms_unpacked.marker
+UNPATCHED_SYSROOT_MARKER := $(BUILDDIR)/sysroot_unpatched.marker
+PATCHED_SYSROOT_MARKER := $(BUILDDIR)/sysroot_patched.marker
+
+################################################################################
+# Download RPMs
+
+ifeq ($(ARCH), armhfp)
+ RPM_ARCH := armv7hl
+else
+ RPM_ARCH := $(ARCH)
+endif
+
+RPM_ARCHS := $(RPM_ARCH) noarch
+ifeq ($(ARCH), x86_64)
+ # Enable mixed mode.
+ RPM_ARCHS += i386 i686
+endif
+
+EMPTY :=
+SPACE := $(EMPTY) $(EMPTY)
+COMMA := ,
+DNF_ARCHS := $(foreach arch,$(RPM_ARCHS),--arch $(arch))
+
+# Specify a dummy installation root, otherwise dnf will run into
+# problems trying to reconcile with the local/system state
+DNF_DUMMY_INSTALL_ROOT := $(BUILDDIR)/dnf-dummy-install-root
+
+DNF_REPOS := $(foreach repo, $(BASE_OS_REPOS), \
+ --repofrompath $(repo) \
+ --enablerepo $(word 1,$(subst $(COMMA),$(SPACE),$(repo))))
+
+DNF_DOWNLOAD_FLAGS := \
+ --disablerepo='*' \
+ $(DNF_REPOS) \
+ --resolve \
+ $(DNF_ARCHS) \
+ --forcearch $(RPM_ARCH) \
+ --installroot $(DNF_DUMMY_INSTALL_ROOT) \
+ --releasever $(BASE_OS_VERSION) \
+ #
+
+$(DOWNLOAD_RPMS_MARKER):
+ @mkdir -p $(@D)
+ mkdir -p $(DOWNLOAD_RPMS)
+ echo $(RPM_LIST) | \
+ xargs dnf download $(DNF_DOWNLOAD_FLAGS) --destdir $(DOWNLOAD_RPMS)
+ touch $@
+
+################################################################################
+# Unpack RPMS
+
+RPM_PATTERNS := $(foreach arch,$(RPM_ARCHS),$(DOWNLOAD_RPMS)/*.$(arch).rpm)
+
+CPIO_EXCLUDES := \
+ "./usr/share/doc/*" \
+ "./usr/share/man/*" \
+ "./usr/X11R6/man/*" \
+ "*/X11/locale/*" \
+ #
+
+$(RPMS_UNPACKED_MARKER): $(DOWNLOAD_RPMS_MARKER)
+ if [ -d $(SYSROOT) ]; then echo "WARNING: Sysroot directory ($(SYSROOT)) already exists, proceeding anyway..."; fi
+ @mkdir -p $(SYSROOT)
+ # The -e test below is needed to skip unmatched glob patterns
+ ( \
+ cd $(SYSROOT); \
+ for rpm in $(RPM_PATTERNS); do \
+ if [ ! -e "$$rpm" ]; then continue; fi; \
+ echo Extracting $$rpm...; \
+ rpm2cpio $$rpm | \
+ cpio --extract --make-directories -f $(CPIO_EXCLUDES) \
+ || exit 1; \
+ done \
+ )
+ touch $@
+
+################################################################################
+
+$(UNPATCHED_SYSROOT_MARKER): $(RPMS_UNPACKED_MARKER)
+ touch $@
+
+################################################################################
+# Patch sysroot
+
+# Note: MUST create a /usr/lib even if not really needed.
+# gcc will use a path relative to it to resolve lib64. (x86_64).
+# we're creating multi-lib compiler with 32bit libc as well, so we should
+# have it anyway, but just to make sure...
+# Patch GNU ld scripts to force linking against libraries in the sysroot
+# and not the ones installed on the build machine.
+
+LD_SCRIPT_PATCHES := \
+ -e 's|/usr/lib64/||g' \
+ -e 's|/usr/lib/||g' \
+ -e 's|/lib64/||g' \
+ -e 's|/lib/||g' \
+ #
+
+$(PATCHED_SYSROOT_MARKER): $(UNPATCHED_SYSROOT_MARKER)
+ @echo Patching GNU ld scripts
+ @( \
+ for f in $$(find $(SYSROOT) -name "*.so" -type f 2>/dev/null); do \
+ if grep -Iq 'GNU ld script' "$$f"; then \
+ sed $(LD_SCRIPT_PATCHES) "$$f" > "$$f.tmp" && \
+ mv "$$f.tmp" "$$f"; \
+ fi; \
+ done \
+ )
+ @mkdir -p $(SYSROOT)/usr/lib
+ @touch $@
+
+################################################################################
+
+download-rpms: $(DOWNLOAD_RPMS_MARKER)
+unpatched-sysroot: $(UNPATCHED_SYSROOT_MARKER)
+sysroot: $(PATCHED_SYSROOT_MARKER)
+
+.PHONY: download-rpms unpatched-sysroot sysroot
diff --git a/make/devkit/Tars.gmk b/make/devkit/Tars.gmk
index 80bba3d0242..c4d1e1f4dc5 100644
--- a/make/devkit/Tars.gmk
+++ b/make/devkit/Tars.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@@ -39,9 +39,9 @@ endif
default: tars
-tars : $(TAR_FILE)
+tars: $(TAR_FILE)
-$(TAR_FILE): $(shell find $(SRC_DIR) -type f)
+$(TAR_FILE): $(shell find $(SRC_DIR) -type f | sed 's/ /\\ /g')
@echo 'Creating compiler package $@'
cd $(dir $(SRC_DIR)) && tar -czf $@ $(notdir $(SRC_DIR))/*
touch $@
diff --git a/make/devkit/Tools.gmk b/make/devkit/Tools.gmk
index 74c6d861777..835ada3a082 100644
--- a/make/devkit/Tools.gmk
+++ b/make/devkit/Tools.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@@ -39,68 +39,14 @@
# Fix this...
#
+include Common.gmk
+
lowercase = $(shell echo $1 | tr A-Z a-z)
$(info TARGET=$(TARGET))
$(info HOST=$(HOST))
$(info BUILD=$(BUILD))
-ARCH := $(word 1,$(subst -, ,$(TARGET)))
-
-ifeq ($(TARGET), arm-linux-gnueabihf)
- ARCH=armhfp
-endif
-
-$(info ARCH=$(ARCH))
-
-KERNEL_HEADERS_RPM := kernel-headers
-
-ifeq ($(BASE_OS), OL)
- ifeq ($(ARCH), aarch64)
- BASE_URL := https://yum.oracle.com/repo/OracleLinux/OL7/6/base/$(ARCH)/
- LINUX_VERSION := OL7.6
- KERNEL_HEADERS_RPM := kernel-uek-headers
- else
- BASE_URL := https://yum.oracle.com/repo/OracleLinux/OL6/4/base/$(ARCH)/
- LINUX_VERSION := OL6.4
- endif
-else ifeq ($(BASE_OS), Fedora)
- DEFAULT_OS_VERSION := 41
- ifeq ($(BASE_OS_VERSION), )
- BASE_OS_VERSION := $(DEFAULT_OS_VERSION)
- endif
- ifeq ($(filter aarch64 armhfp ppc64le riscv64 s390x x86_64, $(ARCH)), )
- $(error Only "aarch64 armhfp ppc64le riscv64 s390x x86_64" architectures are supported for Fedora, but "$(ARCH)" was requested)
- endif
- ifeq ($(ARCH), riscv64)
- ifeq ($(filter 38 39 40 41, $(BASE_OS_VERSION)), )
- $(error Only Fedora 38-41 are supported for "$(ARCH)", but Fedora $(BASE_OS_VERSION) was requested)
- endif
- BASE_URL := http://fedora.riscv.rocks/repos-dist/f$(BASE_OS_VERSION)/latest/$(ARCH)/Packages/
- else
- LATEST_ARCHIVED_OS_VERSION := 41
- ifeq ($(filter aarch64 armhfp x86_64, $(ARCH)), )
- FEDORA_TYPE := fedora-secondary
- else
- FEDORA_TYPE := fedora/linux
- endif
- ifeq ($(ARCH), armhfp)
- ifneq ($(BASE_OS_VERSION), 36)
- $(error Fedora 36 is the last release supporting "armhfp", but $(BASE_OS) was requested)
- endif
- endif
- NOT_ARCHIVED := $(shell [ $(BASE_OS_VERSION) -gt $(LATEST_ARCHIVED_OS_VERSION) ] && echo true)
- ifeq ($(NOT_ARCHIVED),true)
- BASE_URL := https://dl.fedoraproject.org/pub/$(FEDORA_TYPE)/releases/$(BASE_OS_VERSION)/Everything/$(ARCH)/os/Packages/
- else
- BASE_URL := https://archives.fedoraproject.org/pub/archive/$(FEDORA_TYPE)/releases/$(BASE_OS_VERSION)/Everything/$(ARCH)/os/Packages/
- endif
- endif
- LINUX_VERSION := Fedora_$(BASE_OS_VERSION)
-else
- $(error Unknown base OS $(BASE_OS))
-endif
-
################################################################################
# Define external dependencies
@@ -156,32 +102,6 @@ ifneq ($(REQUIRED_MIN_MAKE_MAJOR_VERSION),)
endif
endif
-# RPMs used by all BASE_OS
-RPM_LIST := \
- $(KERNEL_HEADERS_RPM) \
- glibc glibc-headers glibc-devel \
- cups-libs cups-devel \
- libX11 libX11-devel \
- libxcb xorg-x11-proto-devel \
- alsa-lib alsa-lib-devel \
- libXext libXext-devel \
- libXtst libXtst-devel \
- libXrender libXrender-devel \
- libXrandr libXrandr-devel \
- freetype freetype-devel \
- libXt libXt-devel \
- libSM libSM-devel \
- libICE libICE-devel \
- libXi libXi-devel \
- libXdmcp libXdmcp-devel \
- libXau libXau-devel \
- libgcc libxcrypt \
- zlib zlib-devel \
- libffi libffi-devel \
- fontconfig fontconfig-devel \
- systemtap-sdt-devel \
- #
-
################################################################################
# Define common directories and files
@@ -194,28 +114,11 @@ else
endif
# Define directories
-BUILDDIR := $(OUTPUT_ROOT)/$(HOST)/$(TARGET)
-TARGETDIR := $(PREFIX)/$(TARGET)
-SYSROOT := $(TARGETDIR)/sysroot
DOWNLOAD := $(OUTPUT_ROOT)/download
-DOWNLOAD_RPMS := $(DOWNLOAD)/rpms/$(TARGET)-$(LINUX_VERSION)
SRCDIR := $(OUTPUT_ROOT)/src
-# Marker file for unpacking rpms
-RPMS := $(SYSROOT)/rpms_unpacked
-
-# Need to patch libs that are linker scripts to use non-absolute paths
-LIBS := $(SYSROOT)/libs_patched
-
-################################################################################
-# Download RPMs
-download-rpms:
- mkdir -p $(DOWNLOAD_RPMS)
- # Only run this if rpm dir is empty.
- ifeq ($(wildcard $(DOWNLOAD_RPMS)/*.rpm), )
- cd $(DOWNLOAD_RPMS) && \
- wget -r -np -nd $(patsubst %, -A "*%*.rpm", $(RPM_LIST)) $(BASE_URL)
- endif
+# Marker files
+LINK_LIBS_MARKER := $(BUILDDIR)/link_libs.marker
################################################################################
# Unpack source packages
@@ -236,24 +139,24 @@ define DownloadVerify
endif
$(1)_FILE = $(DOWNLOAD)/$(notdir $($(1)_URL))
- $$($(1)_SRC_MARKER) : $$($(1)_FILE)
+ $$($(1)_SRC_MARKER): $$($(1)_FILE)
mkdir -p $$(SRCDIR)
tar -C $$(SRCDIR) -xf $$<
$$(foreach p,$$(abspath $$(wildcard patches/$$(ARCH)-$$(notdir $$($(1)_DIR)).patch)), \
- echo PATCHING $$(p) ; \
- patch -d $$($(1)_DIR) -p1 -i $$(p) ; \
+ echo PATCHING $$(p); \
+ patch -d $$($(1)_DIR) -p1 -i $$(p); \
)
touch $$@
- $$($(1)_FILE) :
+ $$($(1)_FILE):
mkdir -p $$(@D)
wget -O - $$($(1)_URL) > $$@.tmp
sha512_actual="$$$$(sha512sum $$@.tmp | awk '{ print $$$$1; }')"; \
if [ x"$$$${sha512_actual}" != x"$$($(1)_SHA512)" ]; then \
- echo "Checksum mismatch for $$@.tmp"; \
- echo " Expected: $$($(1)_SHA512)"; \
- echo " Actual: $$$${sha512_actual}"; \
- exit 1; \
+ echo "Checksum mismatch for $$@.tmp"; \
+ echo " Expected: $$($(1)_SHA512)"; \
+ echo " Actual: $$$${sha512_actual}"; \
+ exit 1; \
fi
mv $$@.tmp $$@
endef
@@ -261,89 +164,17 @@ endef
# Download and unpack all source packages
$(foreach dep,$(DEPENDENCIES),$(eval $(call DownloadVerify,$(dep))))
-################################################################################
-# Unpack RPMS
-
-RPM_ARCHS := $(ARCH) noarch
-ifeq ($(ARCH),x86_64)
- # Enable mixed mode.
- RPM_ARCHS += i386 i686
-else ifeq ($(ARCH),i686)
- RPM_ARCHS += i386
-else ifeq ($(ARCH), armhfp)
- RPM_ARCHS += armv7hl
-endif
-
-RPM_FILE_LIST := $(sort $(foreach a, $(RPM_ARCHS), \
- $(wildcard $(patsubst %,$(DOWNLOAD_RPMS)/%*$a.rpm,$(RPM_LIST))) \
-))
-
-# Note. For building linux you should install rpm2cpio.
-define unrpm
- $(SYSROOT)/$(notdir $(1)).unpacked : $(1)
- $$(RPMS) : $(SYSROOT)/$(notdir $(1)).unpacked
-endef
-
-%.unpacked :
- $(info Unpacking target rpms and libraries from $<)
- @(mkdir -p $(@D); \
- cd $(@D); \
- rpm2cpio $< | \
- cpio --extract --make-directories \
- -f \
- "./usr/share/doc/*" \
- "./usr/share/man/*" \
- "./usr/X11R6/man/*" \
- "*/X11/locale/*" \
- || die ; )
- touch $@
-
-$(foreach p,$(RPM_FILE_LIST),$(eval $(call unrpm,$(p))))
-
-################################################################################
-
-# Note: MUST create a /usr/lib even if not really needed.
-# gcc will use a path relative to it to resolve lib64. (x86_64).
-# we're creating multi-lib compiler with 32bit libc as well, so we should
-# have it anyway, but just to make sure...
-# Patch libc.so and libpthread.so to force linking against libraries in sysroot
-# and not the ones installed on the build machine.
-$(LIBS) : $(RPMS)
- @echo Patching libc and pthreads
- @(for f in `find $(SYSROOT) -name libc.so -o -name libpthread.so`; do \
- (cat $$f | sed -e 's|/usr/lib64/||g' \
- -e 's|/usr/lib/||g' \
- -e 's|/lib64/||g' \
- -e 's|/lib/||g' ) > $$f.tmp ; \
- mv $$f.tmp $$f ; \
- done)
- @mkdir -p $(SYSROOT)/usr/lib
- @touch $@
-
-################################################################################
-# Create links for ffi header files so that they become visible by default when using the
-# devkit.
-ifeq ($(ARCH), x86_64)
- $(SYSROOT)/usr/include/ffi.h: $(RPMS)
- cd $(@D) && rm -f $(@F) && ln -s ../lib/libffi-*/include/$(@F) .
-
- $(SYSROOT)/usr/include/ffitarget.h: $(RPMS)
- cd $(@D) && rm -f $(@F) && ln -s ../lib/libffi-*/include/$(@F) .
-
- SYSROOT_LINKS += $(SYSROOT)/usr/include/ffi.h $(SYSROOT)/usr/include/ffitarget.h
-endif
-
-################################################################################
-
# Define marker files for each source package to be compiled
$(foreach dep,$(DEPENDENCIES),$(eval $(dep) = $(TARGETDIR)/$($(dep)_VER).done))
################################################################################
# Default base config
-CONFIG = --target=$(TARGET) \
+CONFIG = \
+ --target=$(TARGET) \
--host=$(HOST) --build=$(BUILD) \
- --prefix=$(PREFIX)
+ --prefix=$(PREFIX) \
+ #
CMAKE_CONFIG = -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(PREFIX)
@@ -357,7 +188,6 @@ BUILDPAR = -j$(NUM_CORES)
MAKECMD =
INSTALLCMD = install
-
declare_tools = CC$(1)=$(2)gcc LD$(1)=$(2)ld AR$(1)=$(2)ar AS$(1)=$(2)as RANLIB$(1)=$(2)ranlib CXX$(1)=$(2)g++ OBJDUMP$(1)=$(2)objdump
ifeq ($(HOST),$(BUILD))
@@ -376,10 +206,10 @@ TOOLS ?= $(call declare_tools,_FOR_TARGET,$(TARGET)-)
# CFLAG_ to most likely -m32.
define mk_bfd
$$(info Libs for $(1))
- $$(BUILDDIR)/$$(BINUTILS_VER)-$(subst /,-,$(1))/Makefile \
- : CFLAGS += $$(CFLAGS_$(1))
- $$(BUILDDIR)/$$(BINUTILS_VER)-$(subst /,-,$(1))/Makefile \
- : LIBDIRS = --libdir=$(TARGETDIR)/$(1)
+ $$(BUILDDIR)/$$(BINUTILS_VER)-$(subst /,-,$(1))/Makefile: \
+ CFLAGS += $$(CFLAGS_$(1))
+ $$(BUILDDIR)/$$(BINUTILS_VER)-$(subst /,-,$(1))/Makefile: \
+ LIBDIRS = --libdir=$(TARGETDIR)/$(1)
BFDLIB += $$(TARGETDIR)/$$(BINUTILS_VER)-$(subst /,-,$(1)).done
BFDMAKES += $$(BUILDDIR)/$$(BINUTILS_VER)-$(subst /,-,$(1))/Makefile
@@ -389,18 +219,18 @@ endef
$(foreach l,$(LIBDIRS),$(eval $(call mk_bfd,$(l))))
# Only build these two libs.
-$(BFDLIB) : MAKECMD = all-libiberty all-bfd
-$(BFDLIB) : INSTALLCMD = install-libiberty install-bfd
+$(BFDLIB): MAKECMD = all-libiberty all-bfd
+$(BFDLIB): INSTALLCMD = install-libiberty install-bfd
# Building targets libbfd + libiberty. HOST==TARGET, i.e not
# for a cross env.
-$(BFDMAKES) : CONFIG = --target=$(TARGET) \
+$(BFDMAKES): CONFIG = --target=$(TARGET) \
--host=$(TARGET) --build=$(BUILD) \
--prefix=$(TARGETDIR) \
--with-sysroot=$(SYSROOT) \
$(LIBDIRS)
-$(BFDMAKES) : TOOLS = $(call declare_tools,_FOR_TARGET,$(TARGET)-) $(call declare_tools,,$(TARGET)-)
+$(BFDMAKES): TOOLS = $(call declare_tools,_FOR_TARGET,$(TARGET)-) $(call declare_tools,,$(TARGET)-)
################################################################################
@@ -410,50 +240,52 @@ $(GCC) \
$(MPFR) \
$(MPC) \
$(BFDMAKES) \
- $(CCACHE) : ENVS += $(TOOLS)
+ $(CCACHE): ENVS += $(TOOLS)
# libdir to work around hateful bfd stuff installing into wrong dirs...
# ensure we have 64 bit bfd support in the HOST library. I.e our
# compiler on i686 will know 64 bit symbols, BUT later
# we build just the libs again for TARGET, then with whatever the arch
# wants.
-$(BUILDDIR)/$(BINUTILS_VER)/Makefile : CONFIG += --enable-64-bit-bfd --libdir=$(PREFIX)/$(word 1,$(LIBDIRS))
+$(BUILDDIR)/$(BINUTILS_VER)/Makefile: CONFIG += --enable-64-bit-bfd --libdir=$(PREFIX)/$(word 1,$(LIBDIRS))
ifeq ($(filter $(ARCH), s390x riscv64 ppc64le), )
# gold compiles but cannot link properly on s390x @ gcc 13.2 and Fedore 41
# gold is not available for riscv64 and ppc64le,
# and subsequent linking will fail if we try to enable it.
- LINKER_CONFIG := --enable-gold=default
+ LINKER_CONFIG_ENABLE_GOLD := --enable-gold=default
+endif
+
+ifeq ($(filter riscv64 ppc64le s390x armhfp, $(ARCH)), )
+ ENABLE_MULTILIB := --enable-multilib
endif
# Makefile creation. Simply run configure in build dir.
# Setting CFLAGS to -O2 generates a much faster ld.
$(BFDMAKES) \
-$(BUILDDIR)/$(BINUTILS_VER)/Makefile \
- : $(BINUTILS_CFG)
+$(BUILDDIR)/$(BINUTILS_VER)/Makefile: $(BINUTILS_CFG)
$(info Configuring $@. Log in $(@D)/log.config)
@mkdir -p $(@D)
( \
- cd $(@D) ; \
+ cd $(@D); \
$(PATHPRE) $(ENVS) CFLAGS="-O2 $(CFLAGS)" \
$(BINUTILS_CFG) \
$(CONFIG) \
- $(LINKER_CONFIG) \
+ $(LINKER_CONFIG_ENABLE_GOLD) \
--with-sysroot=$(SYSROOT) \
--disable-nls \
--program-prefix=$(TARGET)- \
- --enable-multilib \
+ $(ENABLE_MULTILIB) \
--enable-threads \
--enable-plugins \
) > $(@D)/log.config 2>&1
@echo 'done'
-$(BUILDDIR)/$(MPFR_VER)/Makefile \
- : $(MPFR_CFG)
+$(BUILDDIR)/$(MPFR_VER)/Makefile: $(MPFR_CFG)
$(info Configuring $@. Log in $(@D)/log.config)
@mkdir -p $(@D)
( \
- cd $(@D) ; \
+ cd $(@D); \
$(PATHPRE) $(ENVS) CFLAGS="$(CFLAGS)" \
$(MPFR_CFG) \
$(CONFIG) \
@@ -463,12 +295,11 @@ $(BUILDDIR)/$(MPFR_VER)/Makefile \
) > $(@D)/log.config 2>&1
@echo 'done'
-$(BUILDDIR)/$(GMP_VER)/Makefile \
- : $(GMP_CFG)
+$(BUILDDIR)/$(GMP_VER)/Makefile: $(GMP_CFG)
$(info Configuring $@. Log in $(@D)/log.config)
@mkdir -p $(@D)
( \
- cd $(@D) ; \
+ cd $(@D); \
$(PATHPRE) $(ENVS) CFLAGS="$(CFLAGS)" \
$(GMP_CFG) \
--host=$(HOST) --build=$(BUILD) \
@@ -480,12 +311,11 @@ $(BUILDDIR)/$(GMP_VER)/Makefile \
) > $(@D)/log.config 2>&1
@echo 'done'
-$(BUILDDIR)/$(MPC_VER)/Makefile \
- : $(MPC_CFG)
+$(BUILDDIR)/$(MPC_VER)/Makefile: $(MPC_CFG)
$(info Configuring $@. Log in $(@D)/log.config)
@mkdir -p $(@D)
( \
- cd $(@D) ; \
+ cd $(@D); \
$(PATHPRE) $(ENVS) CFLAGS="$(CFLAGS)" \
$(MPC_CFG) \
$(CONFIG) \
@@ -499,14 +329,18 @@ $(BUILDDIR)/$(MPC_VER)/Makefile \
# Only valid if glibc target -> linux
# proper destructor handling for c++
ifneq (,$(findstring linux,$(TARGET)))
- $(BUILDDIR)/$(GCC_VER)/Makefile : CONFIG += --enable-__cxa_atexit
+ $(BUILDDIR)/$(GCC_VER)/Makefile: CONFIG += --enable-__cxa_atexit
endif
+$(BUILDDIR)/$(GCC_VER)/Makefile: CONFIG += --disable-libgomp
ifeq ($(ARCH), armhfp)
- $(BUILDDIR)/$(GCC_VER)/Makefile : CONFIG += --with-float=hard
+ $(BUILDDIR)/$(GCC_VER)/Makefile: CONFIG += --with-float=hard
+endif
+ifeq ($(ARCH), riscv64)
+ $(BUILDDIR)/$(GCC_VER)/Makefile: CONFIG += --disable-libsanitizer
endif
-ifneq ($(filter riscv64 ppc64le s390x, $(ARCH)), )
+ifneq ($(filter riscv64 ppc64le s390x armhfp, $(ARCH)), )
# We only support 64-bit on these platforms anyway
CONFIG += --disable-multilib
endif
@@ -518,12 +352,11 @@ endif
# skip native language.
# and link and assemble with the binutils we created
# earlier, so --with-gnu*
-$(BUILDDIR)/$(GCC_VER)/Makefile \
- : $(GCC_CFG)
+$(BUILDDIR)/$(GCC_VER)/Makefile: $(GCC_CFG)
$(info Configuring $@. Log in $(@D)/log.config)
mkdir -p $(@D)
( \
- cd $(@D) ; \
+ cd $(@D); \
$(PATHPRE) $(ENVS) $(GCC_CFG) $(EXTRA_CFLAGS) \
$(CONFIG) \
--with-sysroot=$(SYSROOT) \
@@ -540,12 +373,12 @@ $(BUILDDIR)/$(GCC_VER)/Makefile \
@echo 'done'
# need binutils for gcc
-$(GCC) : $(BINUTILS)
+$(GCC): $(BINUTILS)
# as of 4.3 or so need these for doing config
-$(BUILDDIR)/$(GCC_VER)/Makefile : $(GMP) $(MPFR) $(MPC)
-$(MPFR) : $(GMP)
-$(MPC) : $(GMP) $(MPFR)
+$(BUILDDIR)/$(GCC_VER)/Makefile: $(GMP) $(MPFR) $(MPC)
+$(MPFR): $(GMP)
+$(MPC): $(GMP) $(MPFR)
################################################################################
# Build gdb but only where host and target match
@@ -554,7 +387,7 @@ ifeq ($(HOST), $(TARGET))
$(info Configuring $@. Log in $(@D)/log.config)
mkdir -p $(@D)
( \
- cd $(@D) ; \
+ cd $(@D); \
$(PATHPRE) $(ENVS) CFLAGS="$(CFLAGS)" $(GDB_CFG) \
$(CONFIG) \
--with-sysroot=$(SYSROOT) \
@@ -574,14 +407,12 @@ endif
################################################################################
# very straightforward. just build a ccache. it is only for host.
-$(BUILDDIR)/$(CCACHE_VER)/Makefile \
- : $(CCACHE_SRC_MARKER)
+$(BUILDDIR)/$(CCACHE_VER)/Makefile: $(CCACHE_SRC_MARKER)
$(info Configuring $@. Log in $(@D)/log.config)
@mkdir -p $(@D)
@( \
- cd $(@D) ; \
- $(PATHPRE) $(ENVS) $(CCACHE_CFG) \
- $(CCACHE_CONFIG) \
+ cd $(@D); \
+ $(PATHPRE) $(ENVS) $(CCACHE_CFG) $(CCACHE_CONFIG) \
) > $(@D)/log.config 2>&1
@echo 'done'
@@ -590,13 +421,12 @@ GCC_PATCHED = $(TARGETDIR)/gcc-patched
################################################################################
# For some reason cpp is not created as a target-compiler
ifeq ($(HOST),$(TARGET))
- $(GCC_PATCHED) : $(GCC) link_libs
+ $(GCC_PATCHED): $(LINK_LIBS_MARKER)
@echo -n 'Creating compiler symlinks...'
@for f in cpp; do \
- if [ ! -e $(PREFIX)/bin/$(TARGET)-$$f ]; \
- then \
+ if [ ! -e $(PREFIX)/bin/$(TARGET)-$$f ]; then \
cd $(PREFIX)/bin && \
- ln -fs $$f $(TARGET)-$$f ; \
+ ln -fs $$f $(TARGET)-$$f; \
fi \
done
@touch $@
@@ -606,19 +436,21 @@ ifeq ($(HOST),$(TARGET))
# Ugly at best. Seems that when we compile host->host compiler, that are NOT
# the BUILD compiler, the result will not try searching for libs in package root.
# "Solve" this by create links from the target libdirs to where they are.
- link_libs:
+ $(LINK_LIBS_MARKER): $(GCC)
@echo -n 'Creating library symlinks...'
- @$(foreach l,$(LIBDIRS), \
- for f in `cd $(PREFIX)/$(l) && ls`; do \
- if [ ! -e $(TARGETDIR)/$(l)/$$f ]; then \
- mkdir -p $(TARGETDIR)/$(l) && \
- cd $(TARGETDIR)/$(l)/ && \
- ln -fs $(if $(findstring /,$(l)),../,)../../$(l)/$$f $$f; \
- fi \
- done;)
+ @for l in $(LIBDIRS); do \
+ for f in `cd $(PREFIX)/$$l && ls`; do \
+ if [ ! -e $(TARGETDIR)/$$l/$$f ]; then \
+ mkdir -p $(TARGETDIR)/$$l && \
+ cd $(TARGETDIR)/$$l/ && \
+ ln -fs ../../$$l/$$f $$f; \
+ fi \
+ done \
+ done
+ @touch $@
@echo 'done'
else
- $(GCC_PATCHED) :
+ $(GCC_PATCHED):
@echo 'done'
endif
@@ -628,7 +460,7 @@ endif
# make install.
# Use path to our build hosts cross tools
# Always need to build cross tools for build host self.
-$(TARGETDIR)/%.done : $(BUILDDIR)/%/Makefile
+$(TARGETDIR)/%.done: $(BUILDDIR)/%/Makefile
$(info Building $(basename $@). Log in $( $(&1
@echo -n 'installing...'
@@ -646,26 +478,20 @@ $(PREFIX)/devkit.info:
echo '# This file describes to configure how to interpret the contents of this' >> $@
echo '# devkit' >> $@
echo '' >> $@
- echo 'DEVKIT_NAME="$(GCC_VER) - $(LINUX_VERSION)"' >> $@
+ echo 'DEVKIT_NAME="$(GCC_VER) - $(BASE_OS_DESCRIPTION)"' >> $@
echo 'DEVKIT_TOOLCHAIN_PATH="$$DEVKIT_ROOT/bin"' >> $@
echo 'DEVKIT_SYSROOT="$$DEVKIT_ROOT/$(TARGET)/sysroot"' >> $@
echo 'DEVKIT_EXTRA_PATH="$$DEVKIT_ROOT/bin"' >> $@
################################################################################
# Copy these makefiles into the root of the kit
-$(PREFIX)/Makefile: ./Makefile
- rm -rf $@
- cp $< $@
-$(PREFIX)/Tools.gmk: ./Tools.gmk
- rm -rf $@
- cp $< $@
+THESE_MAKEFILES := Makefile $(wildcard *.gmk) patches
+COPIED_MAKEFILES := $(addprefix $(PREFIX)/, $(THESE_MAKEFILES))
-$(PREFIX)/Tars.gmk: ./Tars.gmk
+$(PREFIX)/%: ./%
rm -rf $@
- cp $< $@
-
-THESE_MAKEFILES := $(PREFIX)/Makefile $(PREFIX)/Tools.gmk $(PREFIX)/Tars.gmk
+ cp -r $< $@
################################################################################
@@ -682,9 +508,14 @@ ifeq ($(TARGET), $(HOST))
@echo 'Creating missing $* soft link'
ln -s $(TARGET)-$* $@
- MISSING_LINKS := $(addprefix $(PREFIX)/bin/, \
+ MISSING_LINK_NAMES = \
addr2line ar as c++ c++filt dwp elfedit g++ gcc gcc-$(GCC_VER_ONLY) gprof ld ld.bfd \
- ld.gold nm objcopy objdump ranlib readelf size strings strip)
+ nm objcopy objdump ranlib readelf size strings strip
+ ifneq ($(LINKER_CONFIG_ENABLE_GOLD), )
+ MISSING_LINK_NAMES += ld.gold
+ endif
+
+ MISSING_LINKS := $(addprefix $(PREFIX)/bin/, $(MISSING_LINK_NAMES))
endif
# Add link to work around "plugin needed to handle lto object" (JDK-8344272)
@@ -697,17 +528,14 @@ MISSING_LINKS += $(PREFIX)/lib/bfd-plugins/liblto_plugin.so
################################################################################
-bfdlib : $(BFDLIB)
-binutils : $(BINUTILS)
-rpms : $(RPMS)
-libs : $(LIBS)
-sysroot : rpms libs
-gcc : sysroot $(GCC) $(GCC_PATCHED)
-gdb : $(GDB)
-all : binutils gcc bfdlib $(PREFIX)/devkit.info $(MISSING_LINKS) $(SYSROOT_LINKS) \
- $(THESE_MAKEFILES) gdb
+bfdlib: $(BFDLIB)
+binutils: $(BINUTILS)
+gcc: $(GCC) $(GCC_PATCHED)
+gdb: $(GDB)
+all: binutils gcc bfdlib gdb \
+ $(MISSING_LINKS) $(COPIED_MAKEFILES) $(PREFIX)/devkit.info
# this is only built for host. so separate.
-ccache : $(CCACHE)
+ccache: $(CCACHE)
-.PHONY : gcc all binutils bfdlib link_libs rpms libs sysroot
+.PHONY: bfdlib binutils gcc gdb all ccache
diff --git a/make/modules/jdk.internal.le/Java.gmk b/make/modules/jdk.internal.le/Java.gmk
index 5067cf677e8..4c4e97bcc5b 100644
--- a/make/modules/jdk.internal.le/Java.gmk
+++ b/make/modules/jdk.internal.le/Java.gmk
@@ -25,7 +25,7 @@
################################################################################
-DISABLED_WARNINGS_java += dangling-doc-comments this-escape suppression
+DISABLED_WARNINGS_java += suppression
COPY += .properties .caps .txt
diff --git a/make/modules/jdk.jpackage/Java.gmk b/make/modules/jdk.jpackage/Java.gmk
index 1a1a0f2e754..1d7a349ce6c 100644
--- a/make/modules/jdk.jpackage/Java.gmk
+++ b/make/modules/jdk.jpackage/Java.gmk
@@ -25,7 +25,7 @@
################################################################################
-DISABLED_WARNINGS_java += dangling-doc-comments suppression
+DISABLED_WARNINGS_java += suppression
COPY += .gif .png .txt .spec .script .prerm .preinst \
.postrm .postinst .list .sh .desktop .copyright .control .plist .template \
diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk
index 47503112b36..9869d39ad11 100644
--- a/make/test/BuildMicrobenchmark.gmk
+++ b/make/test/BuildMicrobenchmark.gmk
@@ -83,8 +83,8 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \
SMALL_JAVA := false, \
CLASSPATH := $(JMH_COMPILE_JARS), \
CREATE_API_DIGEST := true, \
- DISABLED_WARNINGS := restricted this-escape processing rawtypes removal cast \
- serial preview dangling-doc-comments suppression, \
+ DISABLED_WARNINGS := restricted this-escape rawtypes removal cast \
+ serial preview suppression, \
SRC := $(MICROBENCHMARK_SRC), \
BIN := $(MICROBENCHMARK_CLASSES), \
JAVAC_FLAGS := \
diff --git a/make/test/BuildTestLib.gmk b/make/test/BuildTestLib.gmk
index 748710ee230..7470b9e803f 100644
--- a/make/test/BuildTestLib.gmk
+++ b/make/test/BuildTestLib.gmk
@@ -46,7 +46,6 @@ $(eval $(call SetupJavaCompilation, BUILD_WB_JAR, \
SRC := $(TEST_LIB_SOURCE_DIR)/jdk/test/whitebox/, \
BIN := $(TEST_LIB_SUPPORT)/wb_classes, \
JAR := $(TEST_LIB_SUPPORT)/wb.jar, \
- DISABLED_WARNINGS := deprecation removal preview, \
JAVAC_FLAGS := --enable-preview, \
))
diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad
index 2ff93c9e288..b9899995531 100644
--- a/src/hotspot/cpu/aarch64/aarch64_vector.ad
+++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad
@@ -1671,24 +1671,42 @@ instruct vnotL(vReg dst, vReg src, immL_M1 m1) %{
// vector not - predicated
-instruct vnotI_masked(vReg dst_src, immI_M1 m1, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vnotI_masked(vReg dst, vReg src, immI_M1 m1, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (XorV (Binary dst_src (Replicate m1)) pg));
- format %{ "vnotI_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (XorV (Binary src (Replicate m1)) pg));
+ format %{ "vnotI_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_not($dst_src$$FloatRegister, get_reg_variant(this),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_not($dst$$FloatRegister, get_reg_variant(this),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vnotL_masked(vReg dst_src, immL_M1 m1, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vnotL_masked(vReg dst, vReg src, immL_M1 m1, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (XorV (Binary dst_src (Replicate m1)) pg));
- format %{ "vnotL_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (XorV (Binary src (Replicate m1)) pg));
+ format %{ "vnotL_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_not($dst_src$$FloatRegister, get_reg_variant(this),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_not($dst$$FloatRegister, get_reg_variant(this),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -1985,62 +2003,116 @@ instruct vabsD(vReg dst, vReg src) %{
// vector abs - predicated
-instruct vabsB_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vabsB_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (AbsVB dst_src pg));
- format %{ "vabsB_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (AbsVB src pg));
+ format %{ "vabsB_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_abs($dst_src$$FloatRegister, __ B, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_abs($dst$$FloatRegister, __ B, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vabsS_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vabsS_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (AbsVS dst_src pg));
- format %{ "vabsS_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (AbsVS src pg));
+ format %{ "vabsS_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_abs($dst_src$$FloatRegister, __ H, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_abs($dst$$FloatRegister, __ H, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vabsI_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vabsI_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (AbsVI dst_src pg));
- format %{ "vabsI_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (AbsVI src pg));
+ format %{ "vabsI_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_abs($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_abs($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vabsL_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vabsL_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (AbsVL dst_src pg));
- format %{ "vabsL_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (AbsVL src pg));
+ format %{ "vabsL_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_abs($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_abs($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vabsF_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vabsF_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (AbsVF dst_src pg));
- format %{ "vabsF_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (AbsVF src pg));
+ format %{ "vabsF_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_fabs($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_fabs($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vabsD_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vabsD_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (AbsVD dst_src pg));
- format %{ "vabsD_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (AbsVD src pg));
+ format %{ "vabsD_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_fabs($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_fabs($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -2158,44 +2230,80 @@ instruct vnegD(vReg dst, vReg src) %{
// vector neg - predicated
-instruct vnegI_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vnegI_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (NegVI dst_src pg));
- format %{ "vnegI_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (NegVI src pg));
+ format %{ "vnegI_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
- __ sve_neg($dst_src$$FloatRegister, __ elemType_to_regVariant(bt),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_neg($dst$$FloatRegister, __ elemType_to_regVariant(bt),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vnegL_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vnegL_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (NegVL dst_src pg));
- format %{ "vnegL_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (NegVL src pg));
+ format %{ "vnegL_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_neg($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_neg($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vnegF_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vnegF_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (NegVF dst_src pg));
- format %{ "vnegF_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (NegVF src pg));
+ format %{ "vnegF_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_fneg($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_fneg($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vnegD_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vnegD_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (NegVD dst_src pg));
- format %{ "vnegD_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (NegVD src pg));
+ format %{ "vnegD_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_fneg($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_fneg($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -2251,22 +2359,40 @@ instruct vsqrtD(vReg dst, vReg src) %{
// vector sqrt - predicated
-instruct vsqrtF_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vsqrtF_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (SqrtVF dst_src pg));
- format %{ "vsqrtF_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (SqrtVF src pg));
+ format %{ "vsqrtF_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_fsqrt($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_fsqrt($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vsqrtD_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vsqrtD_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (SqrtVD dst_src pg));
- format %{ "vsqrtD_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (SqrtVD src pg));
+ format %{ "vsqrtD_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_fsqrt($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_fsqrt($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -5331,9 +5457,7 @@ instruct insertI_index_lt32(vReg dst, vReg src, iRegIorL2I val, immI idx,
__ sve_index($tmp$$FloatRegister, size, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue,
$tmp$$FloatRegister, (int)($idx$$constant) - 16);
- if ($dst$$FloatRegister != $src$$FloatRegister) {
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- }
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register);
%}
ins_pipe(pipe_slow);
@@ -5356,9 +5480,7 @@ instruct insertI_index_ge32(vReg dst, vReg src, iRegIorL2I val, immI idx, vReg t
__ sve_dup($tmp2$$FloatRegister, size, (int)($idx$$constant));
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue,
$tmp1$$FloatRegister, $tmp2$$FloatRegister);
- if ($dst$$FloatRegister != $src$$FloatRegister) {
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- }
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register);
%}
ins_pipe(pipe_slow);
@@ -5392,9 +5514,7 @@ instruct insertL_gt128b(vReg dst, vReg src, iRegL val, immI idx,
__ sve_index($tmp$$FloatRegister, __ D, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue,
$tmp$$FloatRegister, (int)($idx$$constant) - 16);
- if ($dst$$FloatRegister != $src$$FloatRegister) {
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- }
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$Register);
%}
ins_pipe(pipe_slow);
@@ -5432,7 +5552,7 @@ instruct insertF_index_lt32(vReg dst, vReg src, vRegF val, immI idx,
__ sve_index($dst$$FloatRegister, __ S, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue,
$dst$$FloatRegister, (int)($idx$$constant) - 16);
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister);
%}
ins_pipe(pipe_slow);
@@ -5451,7 +5571,7 @@ instruct insertF_index_ge32(vReg dst, vReg src, vRegF val, immI idx, vReg tmp,
__ sve_dup($dst$$FloatRegister, __ S, (int)($idx$$constant));
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue,
$tmp$$FloatRegister, $dst$$FloatRegister);
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister);
%}
ins_pipe(pipe_slow);
@@ -5486,7 +5606,7 @@ instruct insertD_gt128b(vReg dst, vReg src, vRegD val, immI idx,
__ sve_index($dst$$FloatRegister, __ D, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue,
$dst$$FloatRegister, (int)($idx$$constant) - 16);
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$FloatRegister);
%}
ins_pipe(pipe_slow);
@@ -5656,8 +5776,12 @@ instruct extractF(vRegF dst, vReg src, immI idx) %{
__ ins($dst$$FloatRegister, __ S, $src$$FloatRegister, 0, index);
} else {
assert(UseSVE > 0, "must be sve");
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- __ sve_ext($dst$$FloatRegister, $dst$$FloatRegister, index << 2);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the second source of ext. The movprfx destination register
+ // must not appear in any source operand of the following instruction
+ // except as the destructive operand.
+ __ sve_ext($dst$$FloatRegister, $src$$FloatRegister, index << 2);
}
%}
ins_pipe(pipe_slow);
@@ -5677,8 +5801,12 @@ instruct extractD(vRegD dst, vReg src, immI idx) %{
__ ins($dst$$FloatRegister, __ D, $src$$FloatRegister, 0, index);
} else {
assert(UseSVE > 0, "must be sve");
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- __ sve_ext($dst$$FloatRegister, $dst$$FloatRegister, index << 3);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the second source of ext. The movprfx destination register
+ // must not appear in any source operand of the following instruction
+ // except as the destructive operand.
+ __ sve_ext($dst$$FloatRegister, $src$$FloatRegister, index << 3);
}
%}
ins_pipe(pipe_slow);
@@ -6855,25 +6983,43 @@ instruct vpopcountL(vReg dst, vReg src) %{
// vector popcount - predicated
-instruct vpopcountI_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vpopcountI_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (PopCountVI dst_src pg));
- format %{ "vpopcountI_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (PopCountVI src pg));
+ format %{ "vpopcountI_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
- __ sve_cnt($dst_src$$FloatRegister, __ elemType_to_regVariant(bt),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_cnt($dst$$FloatRegister, __ elemType_to_regVariant(bt),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
-instruct vpopcountL_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vpopcountL_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (PopCountVL dst_src pg));
- format %{ "vpopcountL_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (PopCountVL src pg));
+ format %{ "vpopcountL_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_cnt($dst_src$$FloatRegister, __ D,
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_cnt($dst$$FloatRegister, __ D,
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -7240,14 +7386,23 @@ instruct vcountLeadingZeros(vReg dst, vReg src) %{
// The dst and src should use the same register to make sure the
// inactive lanes in dst save the same elements as src.
-instruct vcountLeadingZeros_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vcountLeadingZeros_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (CountLeadingZerosV dst_src pg));
- format %{ "vcountLeadingZeros_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (CountLeadingZerosV src pg));
+ format %{ "vcountLeadingZeros_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
- __ sve_clz($dst_src$$FloatRegister, __ elemType_to_regVariant(bt),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_clz($dst$$FloatRegister, __ elemType_to_regVariant(bt),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -7296,19 +7451,26 @@ instruct vcountTrailingZeros(vReg dst, vReg src) %{
ins_pipe(pipe_slow);
%}
-// The dst and src should use the same register to make sure the
-// inactive lanes in dst save the same elements as src.
-instruct vcountTrailingZeros_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vcountTrailingZeros_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (CountTrailingZerosV dst_src pg));
- format %{ "vcountTrailingZeros_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (CountTrailingZerosV src pg));
+ format %{ "vcountTrailingZeros_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
Assembler::SIMD_RegVariant size = __ elemType_to_regVariant(bt);
- __ sve_rbit($dst_src$$FloatRegister, size,
- $pg$$PRegister, $dst_src$$FloatRegister);
- __ sve_clz($dst_src$$FloatRegister, size,
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_rbit($dst$$FloatRegister, size,
+ $pg$$PRegister, $src$$FloatRegister);
+ __ sve_clz($dst$$FloatRegister, size,
+ $pg$$PRegister, $dst$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -7347,14 +7509,23 @@ instruct vreverse(vReg dst, vReg src) %{
// The dst and src should use the same register to make sure the
// inactive lanes in dst save the same elements as src.
-instruct vreverse_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vreverse_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (ReverseV dst_src pg));
- format %{ "vreverse_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (ReverseV src pg));
+ format %{ "vreverse_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
- __ sve_rbit($dst_src$$FloatRegister, __ elemType_to_regVariant(bt),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_rbit($dst$$FloatRegister, __ elemType_to_regVariant(bt),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -7393,19 +7564,28 @@ instruct vreverseBytes(vReg dst, vReg src) %{
ins_pipe(pipe_slow);
%}
-// The dst and src should use the same register to make sure the
-// inactive lanes in dst save the same elements as src.
-instruct vreverseBytes_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vreverseBytes_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (ReverseBytesV dst_src pg));
- format %{ "vreverseBytes_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (ReverseBytesV src pg));
+ format %{ "vreverseBytes_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
if (bt == T_BYTE) {
- // do nothing
+ if ($dst$$FloatRegister != $src$$FloatRegister) {
+ __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ }
} else {
- __ sve_revb($dst_src$$FloatRegister, __ elemType_to_regVariant(bt),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_revb($dst$$FloatRegister, __ elemType_to_regVariant(bt),
+ $pg$$PRegister, $src$$FloatRegister);
}
%}
ins_pipe(pipe_slow);
diff --git a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4
index c5df949dfb6..a53efd43d5d 100644
--- a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4
+++ b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4
@@ -899,13 +899,22 @@ dnl
dnl VECTOR_NOT_PREDICATE($1 )
dnl VECTOR_NOT_PREDICATE(type)
define(`VECTOR_NOT_PREDICATE', `
-instruct vnot$1_masked`'(vReg dst_src, imm$1_M1 m1, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vnot$1_masked`'(vReg dst, vReg src, imm$1_M1 m1, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (XorV (Binary dst_src (Replicate m1)) pg));
- format %{ "vnot$1_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (XorV (Binary src (Replicate m1)) pg));
+ format %{ "vnot$1_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_not($dst_src$$FloatRegister, get_reg_variant(this),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_not($dst$$FloatRegister, get_reg_variant(this),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}')dnl
@@ -1042,14 +1051,23 @@ dnl
dnl UNARY_OP_PREDICATE($1, $2, $3 )
dnl UNARY_OP_PREDICATE(rule_name, op_name, insn)
define(`UNARY_OP_PREDICATE', `
-instruct $1_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct $1_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src ($2 dst_src pg));
- format %{ "$1_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst ($2 src pg));
+ format %{ "$1_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
- __ $3($dst_src$$FloatRegister, __ elemType_to_regVariant(bt),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ $3($dst$$FloatRegister, __ elemType_to_regVariant(bt),
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}')dnl
@@ -1057,12 +1075,21 @@ dnl
dnl UNARY_OP_PREDICATE_WITH_SIZE($1, $2, $3, $4 )
dnl UNARY_OP_PREDICATE_WITH_SIZE(rule_name, op_name, insn, size)
define(`UNARY_OP_PREDICATE_WITH_SIZE', `
-instruct $1_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct $1_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src ($2 dst_src pg));
- format %{ "$1_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst ($2 src pg));
+ format %{ "$1_masked $dst, $pg, $src" %}
ins_encode %{
- __ $3($dst_src$$FloatRegister, __ $4, $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ $3($dst$$FloatRegister, __ $4, $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}')dnl
@@ -3368,9 +3395,7 @@ instruct insertI_index_lt32(vReg dst, vReg src, iRegIorL2I val, immI idx,
__ sve_index($tmp$$FloatRegister, size, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue,
$tmp$$FloatRegister, (int)($idx$$constant) - 16);
- if ($dst$$FloatRegister != $src$$FloatRegister) {
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- }
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register);
%}
ins_pipe(pipe_slow);
@@ -3393,9 +3418,7 @@ instruct insertI_index_ge32(vReg dst, vReg src, iRegIorL2I val, immI idx, vReg t
__ sve_dup($tmp2$$FloatRegister, size, (int)($idx$$constant));
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue,
$tmp1$$FloatRegister, $tmp2$$FloatRegister);
- if ($dst$$FloatRegister != $src$$FloatRegister) {
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- }
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register);
%}
ins_pipe(pipe_slow);
@@ -3429,9 +3452,7 @@ instruct insertL_gt128b(vReg dst, vReg src, iRegL val, immI idx,
__ sve_index($tmp$$FloatRegister, __ D, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue,
$tmp$$FloatRegister, (int)($idx$$constant) - 16);
- if ($dst$$FloatRegister != $src$$FloatRegister) {
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- }
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$Register);
%}
ins_pipe(pipe_slow);
@@ -3469,7 +3490,7 @@ instruct insertF_index_lt32(vReg dst, vReg src, vRegF val, immI idx,
__ sve_index($dst$$FloatRegister, __ S, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue,
$dst$$FloatRegister, (int)($idx$$constant) - 16);
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister);
%}
ins_pipe(pipe_slow);
@@ -3488,7 +3509,7 @@ instruct insertF_index_ge32(vReg dst, vReg src, vRegF val, immI idx, vReg tmp,
__ sve_dup($dst$$FloatRegister, __ S, (int)($idx$$constant));
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue,
$tmp$$FloatRegister, $dst$$FloatRegister);
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister);
%}
ins_pipe(pipe_slow);
@@ -3523,7 +3544,7 @@ instruct insertD_gt128b(vReg dst, vReg src, vRegD val, immI idx,
__ sve_index($dst$$FloatRegister, __ D, -16, 1);
__ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue,
$dst$$FloatRegister, (int)($idx$$constant) - 16);
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
__ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$FloatRegister);
%}
ins_pipe(pipe_slow);
@@ -3621,8 +3642,12 @@ instruct extract$1(vReg$1 dst, vReg src, immI idx) %{
__ ins($dst$$FloatRegister, __ $4, $src$$FloatRegister, 0, index);
} else {
assert(UseSVE > 0, "must be sve");
- __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
- __ sve_ext($dst$$FloatRegister, $dst$$FloatRegister, index << $5);
+ __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the second source of ext. The movprfx destination register
+ // must not appear in any source operand of the following instruction
+ // except as the destructive operand.
+ __ sve_ext($dst$$FloatRegister, $src$$FloatRegister, index << $5);
}
%}
ins_pipe(pipe_slow);
@@ -4682,13 +4707,22 @@ instruct vpopcountL(vReg dst, vReg src) %{
// vector popcount - predicated
UNARY_OP_PREDICATE(vpopcountI, PopCountVI, sve_cnt)
-instruct vpopcountL_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vpopcountL_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (PopCountVL dst_src pg));
- format %{ "vpopcountL_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (PopCountVL src pg));
+ format %{ "vpopcountL_masked $dst, $pg, $src" %}
ins_encode %{
- __ sve_cnt($dst_src$$FloatRegister, __ D,
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_cnt($dst$$FloatRegister, __ D,
+ $pg$$PRegister, $src$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -5100,19 +5134,26 @@ instruct vcountTrailingZeros(vReg dst, vReg src) %{
ins_pipe(pipe_slow);
%}
-// The dst and src should use the same register to make sure the
-// inactive lanes in dst save the same elements as src.
-instruct vcountTrailingZeros_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vcountTrailingZeros_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (CountTrailingZerosV dst_src pg));
- format %{ "vcountTrailingZeros_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (CountTrailingZerosV src pg));
+ format %{ "vcountTrailingZeros_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
Assembler::SIMD_RegVariant size = __ elemType_to_regVariant(bt);
- __ sve_rbit($dst_src$$FloatRegister, size,
- $pg$$PRegister, $dst_src$$FloatRegister);
- __ sve_clz($dst_src$$FloatRegister, size,
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_rbit($dst$$FloatRegister, size,
+ $pg$$PRegister, $src$$FloatRegister);
+ __ sve_clz($dst$$FloatRegister, size,
+ $pg$$PRegister, $dst$$FloatRegister);
%}
ins_pipe(pipe_slow);
%}
@@ -5186,19 +5227,28 @@ instruct vreverseBytes(vReg dst, vReg src) %{
ins_pipe(pipe_slow);
%}
-// The dst and src should use the same register to make sure the
-// inactive lanes in dst save the same elements as src.
-instruct vreverseBytes_masked(vReg dst_src, pRegGov pg) %{
+// The Java Vector API specification requires that for masked unary operations,
+// suppressed lanes are filled from the first vector operand (see "Masked
+// Operations" in Vector.java around line 568). So we use movprfx to copy src
+// into dst before emitting the predicated instruction.
+instruct vreverseBytes_masked(vReg dst, vReg src, pRegGov pg) %{
predicate(UseSVE > 0);
- match(Set dst_src (ReverseBytesV dst_src pg));
- format %{ "vreverseBytes_masked $dst_src, $pg, $dst_src" %}
+ match(Set dst (ReverseBytesV src pg));
+ format %{ "vreverseBytes_masked $dst, $pg, $src" %}
ins_encode %{
BasicType bt = Matcher::vector_element_basic_type(this);
if (bt == T_BYTE) {
- // do nothing
+ if ($dst$$FloatRegister != $src$$FloatRegister) {
+ __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister);
+ }
} else {
- __ sve_revb($dst_src$$FloatRegister, __ elemType_to_regVariant(bt),
- $pg$$PRegister, $dst_src$$FloatRegister);
+ __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister);
+ // Although dst and src hold the same value after movprfx, we must use src
+ // (not dst) as the source of the following instruction. The movprfx
+ // destination register must not appear in any source operand of the
+ // following instruction except as the destructive operand.
+ __ sve_revb($dst$$FloatRegister, __ elemType_to_regVariant(bt),
+ $pg$$PRegister, $src$$FloatRegister);
}
%}
ins_pipe(pipe_slow);
diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
index 67dc4966d64..cb9e308197e 100644
--- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
@@ -2494,8 +2494,12 @@ void C2_MacroAssembler::sve_extract_integral(Register dst, BasicType bt, FloatRe
smov(dst, src, size, idx);
}
} else {
- sve_orr(vtmp, src, src);
- sve_ext(vtmp, vtmp, idx << size);
+ sve_movprfx(vtmp, src);
+ // Although vtmp and src hold the same value after movprfx, we must use src
+ // (not vtmp) as the second source of ext. The movprfx destination register
+ // must not appear in any source operand of the following instruction except
+ // as the destructive operand.
+ sve_ext(vtmp, src, idx << size);
if (bt == T_INT || bt == T_LONG) {
umov(dst, vtmp, size, 0);
} else {
diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
index 56ac2eec0a9..c590b6699c0 100644
--- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
@@ -28,7 +28,6 @@
#include "gc/shenandoah/mode/shenandoahMode.hpp"
#include "gc/shenandoah/shenandoahBarrierSet.hpp"
#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp"
-#include "gc/shenandoah/shenandoahForwarding.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
#include "gc/shenandoah/shenandoahRuntime.hpp"
@@ -174,53 +173,6 @@ void ShenandoahBarrierSetAssembler::satb_barrier(MacroAssembler* masm,
__ bind(done);
}
-void ShenandoahBarrierSetAssembler::resolve_forward_pointer(MacroAssembler* masm, Register dst, Register tmp) {
- assert(ShenandoahLoadRefBarrier || ShenandoahCASBarrier, "Should be enabled");
- Label is_null;
- __ cbz(dst, is_null);
- resolve_forward_pointer_not_null(masm, dst, tmp);
- __ bind(is_null);
-}
-
-// IMPORTANT: This must preserve all registers, even rscratch1 and rscratch2, except those explicitly
-// passed in.
-void ShenandoahBarrierSetAssembler::resolve_forward_pointer_not_null(MacroAssembler* masm, Register dst, Register tmp) {
- assert(ShenandoahLoadRefBarrier || ShenandoahCASBarrier, "Should be enabled");
- // The below loads the mark word, checks if the lowest two bits are
- // set, and if so, clear the lowest two bits and copy the result
- // to dst. Otherwise it leaves dst alone.
- // Implementing this is surprisingly awkward. I do it here by:
- // - Inverting the mark word
- // - Test lowest two bits == 0
- // - If so, set the lowest two bits
- // - Invert the result back, and copy to dst
-
- bool borrow_reg = (tmp == noreg);
- if (borrow_reg) {
- // No free registers available. Make one useful.
- tmp = rscratch1;
- if (tmp == dst) {
- tmp = rscratch2;
- }
- __ push(RegSet::of(tmp), sp);
- }
-
- assert_different_registers(tmp, dst);
-
- Label done;
- __ ldr(tmp, Address(dst, oopDesc::mark_offset_in_bytes()));
- __ eon(tmp, tmp, zr);
- __ ands(zr, tmp, markWord::lock_mask_in_place);
- __ br(Assembler::NE, done);
- __ orr(tmp, tmp, markWord::marked_value);
- __ eon(dst, tmp, zr);
- __ bind(done);
-
- if (borrow_reg) {
- __ pop(RegSet::of(tmp), sp);
- }
-}
-
void ShenandoahBarrierSetAssembler::load_reference_barrier(MacroAssembler* masm, Register dst, Address load_addr, DecoratorSet decorators) {
assert(ShenandoahLoadRefBarrier, "Should be enabled");
assert(dst != rscratch2, "need rscratch2");
@@ -468,166 +420,6 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl
__ bind(done);
}
-// Special Shenandoah CAS implementation that handles false negatives due
-// to concurrent evacuation. The service is more complex than a
-// traditional CAS operation because the CAS operation is intended to
-// succeed if the reference at addr exactly matches expected or if the
-// reference at addr holds a pointer to a from-space object that has
-// been relocated to the location named by expected. There are two
-// races that must be addressed:
-// a) A parallel thread may mutate the contents of addr so that it points
-// to a different object. In this case, the CAS operation should fail.
-// b) A parallel thread may heal the contents of addr, replacing a
-// from-space pointer held in addr with the to-space pointer
-// representing the new location of the object.
-// Upon entry to cmpxchg_oop, it is assured that new_val equals null
-// or it refers to an object that is not being evacuated out of
-// from-space, or it refers to the to-space version of an object that
-// is being evacuated out of from-space.
-//
-// By default the value held in the result register following execution
-// of the generated code sequence is 0 to indicate failure of CAS,
-// non-zero to indicate success. If is_cae, the result is the value most
-// recently fetched from addr rather than a boolean success indicator.
-//
-// Clobbers rscratch1, rscratch2
-void ShenandoahBarrierSetAssembler::cmpxchg_oop(MacroAssembler* masm,
- Register addr,
- Register expected,
- Register new_val,
- bool acquire, bool release,
- bool is_cae,
- Register result) {
- Register tmp1 = rscratch1;
- Register tmp2 = rscratch2;
- bool is_narrow = UseCompressedOops;
- Assembler::operand_size size = is_narrow ? Assembler::word : Assembler::xword;
-
- assert_different_registers(addr, expected, tmp1, tmp2);
- assert_different_registers(addr, new_val, tmp1, tmp2);
-
- Label step4, done;
-
- // There are two ways to reach this label. Initial entry into the
- // cmpxchg_oop code expansion starts at step1 (which is equivalent
- // to label step4). Additionally, in the rare case that four steps
- // are required to perform the requested operation, the fourth step
- // is the same as the first. On a second pass through step 1,
- // control may flow through step 2 on its way to failure. It will
- // not flow from step 2 to step 3 since we are assured that the
- // memory at addr no longer holds a from-space pointer.
- //
- // The comments that immediately follow the step4 label apply only
- // to the case in which control reaches this label by branch from
- // step 3.
-
- __ bind (step4);
-
- // Step 4. CAS has failed because the value most recently fetched
- // from addr is no longer the from-space pointer held in tmp2. If a
- // different thread replaced the in-memory value with its equivalent
- // to-space pointer, then CAS may still be able to succeed. The
- // value held in the expected register has not changed.
- //
- // It is extremely rare we reach this point. For this reason, the
- // implementation opts for smaller rather than potentially faster
- // code. Ultimately, smaller code for this rare case most likely
- // delivers higher overall throughput by enabling improved icache
- // performance.
-
- // Step 1. Fast-path.
- //
- // Try to CAS with given arguments. If successful, then we are done.
- //
- // No label required for step 1.
-
- __ cmpxchg(addr, expected, new_val, size, acquire, release, false, tmp2);
- // EQ flag set iff success. tmp2 holds value fetched.
-
- // If expected equals null but tmp2 does not equal null, the
- // following branches to done to report failure of CAS. If both
- // expected and tmp2 equal null, the following branches to done to
- // report success of CAS. There's no need for a special test of
- // expected equal to null.
-
- __ br(Assembler::EQ, done);
- // if CAS failed, fall through to step 2
-
- // Step 2. CAS has failed because the value held at addr does not
- // match expected. This may be a false negative because the value fetched
- // from addr (now held in tmp2) may be a from-space pointer to the
- // original copy of same object referenced by to-space pointer expected.
- //
- // To resolve this, it suffices to find the forward pointer associated
- // with fetched value. If this matches expected, retry CAS with new
- // parameters. If this mismatches, then we have a legitimate
- // failure, and we're done.
- //
- // No need for step2 label.
-
- // overwrite tmp1 with from-space pointer fetched from memory
- __ mov(tmp1, tmp2);
-
- if (is_narrow) {
- // Decode tmp1 in order to resolve its forward pointer
- __ decode_heap_oop(tmp1, tmp1);
- }
- resolve_forward_pointer(masm, tmp1);
- // Encode tmp1 to compare against expected.
- __ encode_heap_oop(tmp1, tmp1);
-
- // Does forwarded value of fetched from-space pointer match original
- // value of expected? If tmp1 holds null, this comparison will fail
- // because we know from step1 that expected is not null. There is
- // no need for a separate test for tmp1 (the value originally held
- // in memory) equal to null.
- __ cmp(tmp1, expected);
-
- // If not, then the failure was legitimate and we're done.
- // Branching to done with NE condition denotes failure.
- __ br(Assembler::NE, done);
-
- // Fall through to step 3. No need for step3 label.
-
- // Step 3. We've confirmed that the value originally held in memory
- // (now held in tmp2) pointed to from-space version of original
- // expected value. Try the CAS again with the from-space expected
- // value. If it now succeeds, we're good.
- //
- // Note: tmp2 holds encoded from-space pointer that matches to-space
- // object residing at expected. tmp2 is the new "expected".
-
- // Note that macro implementation of __cmpxchg cannot use same register
- // tmp2 for result and expected since it overwrites result before it
- // compares result with expected.
- __ cmpxchg(addr, tmp2, new_val, size, acquire, release, false, noreg);
- // EQ flag set iff success. tmp2 holds value fetched, tmp1 (rscratch1) clobbered.
-
- // If fetched value did not equal the new expected, this could
- // still be a false negative because some other thread may have
- // newly overwritten the memory value with its to-space equivalent.
- __ br(Assembler::NE, step4);
-
- if (is_cae) {
- // We're falling through to done to indicate success. Success
- // with is_cae is denoted by returning the value of expected as
- // result.
- __ mov(tmp2, expected);
- }
-
- __ bind(done);
- // At entry to done, the Z (EQ) flag is on iff if the CAS
- // operation was successful. Additionally, if is_cae, tmp2 holds
- // the value most recently fetched from addr. In this case, success
- // is denoted by tmp2 matching expected.
-
- if (is_cae) {
- __ mov(result, tmp2);
- } else {
- __ cset(result, Assembler::EQ);
- }
-}
-
void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
Register start, Register count, Register scratch) {
assert(ShenandoahCardBarrier, "Should have been checked by caller");
@@ -868,7 +660,7 @@ void ShenandoahBarrierSetAssembler::load_c2(const MachNode* node, MacroAssembler
void ShenandoahBarrierSetAssembler::store_c2(const MachNode* node, MacroAssembler* masm, Address dst, bool dst_narrow,
Register src, bool src_narrow, Register tmp1, Register tmp2, Register tmp3, bool is_volatile) {
- ShenandoahBarrierStubC2::store_pre(masm, node, tmp1, dst, tmp2, tmp3, dst_narrow);
+ ShenandoahBarrierStubC2::store_pre(masm, node, dst, tmp1, tmp2, tmp3, dst_narrow);
// Do the actual store
if (dst_narrow) {
@@ -906,7 +698,7 @@ void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, Mac
Register oldval, Register newval, Register tmp1, Register tmp2, Register tmp3, bool exchange, bool narrow, bool weak, bool acquire) {
Assembler::operand_size op_size = narrow ? Assembler::word : Assembler::xword;
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, addr, tmp2, tmp3, narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, tmp1, tmp2, tmp3, narrow);
// CAS!
__ cmpxchg(addr, oldval, newval, op_size, acquire, /* release */ true, weak, exchange ? res : noreg);
@@ -924,7 +716,7 @@ void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAs
Register newval, Register addr, Register tmp1, Register tmp2, Register tmp3, bool is_acquire) {
bool is_narrow = node->bottom_type()->isa_narrowoop();
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, addr, tmp2, tmp3, is_narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, tmp1, tmp2, tmp3, is_narrow);
if (is_narrow) {
if (is_acquire) {
@@ -1184,12 +976,9 @@ void ShenandoahBarrierStubC2::lrb(MacroAssembler& masm) {
if (c_rarg0 == _obj) {
__ lea(c_rarg1, _addr);
} else if (c_rarg1 == _obj) {
- // Set up arguments in reverse, and then flip them
- __ lea(c_rarg0, _addr);
- // flip them
- __ mov(_tmp1, c_rarg0);
- __ mov(c_rarg0, c_rarg1);
- __ mov(c_rarg1, _tmp1);
+ __ mov(_tmp1, c_rarg1);
+ __ lea(c_rarg1, _addr);
+ __ mov(c_rarg0, _tmp1);
} else {
assert_different_registers(c_rarg1, _obj);
__ lea(c_rarg1, _addr);
diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp
index e4c7007eb17..bab4fb3b37a 100644
--- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp
@@ -53,8 +53,6 @@ private:
void card_barrier(MacroAssembler* masm, Register obj);
- void resolve_forward_pointer(MacroAssembler* masm, Register dst, Register tmp = noreg);
- void resolve_forward_pointer_not_null(MacroAssembler* masm, Register dst, Register tmp = noreg);
void load_reference_barrier(MacroAssembler* masm, Register dst, Address load_addr, DecoratorSet decorators);
void gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
@@ -76,8 +74,6 @@ public:
Register obj, Register tmp, Label& slowpath);
virtual void try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj,
Register tmp, Label& slow_path);
- void cmpxchg_oop(MacroAssembler* masm, Register addr, Register expected, Register new_val,
- bool acquire, bool release, bool is_cae, Register result);
#ifdef COMPILER1
void gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub);
diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
index a52ad112560..1c052b67503 100644
--- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
@@ -2147,7 +2147,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
Register offset = rscratch2;
Label L_loop_search_receiver, L_loop_search_empty;
- Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update;
+ Label L_restart, L_found_recv, L_found_empty, L_count_update;
// The code here recognizes three major cases:
// A. Fastest: receiver found in the table
@@ -2177,21 +2177,20 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
// if (receiver(i) == recv) goto found_recv(i);
// }
//
- // // Fast: no receiver, but profile is full
+ // // Fast: no receiver, but profile is not full
// for (i = 0; i < receiver_count(); i++) {
// if (receiver(i) == null) goto found_null(i);
// }
- // goto polymorphic
+ //
+ // // Slow: profile is full, polymorphic case
+ // count++;
+ // return
//
// // Slow: try to install receiver
// found_null(i):
// CAS(&receiver(i), null, recv);
// goto restart
//
- // polymorphic:
- // count++;
- // return
- //
// found_recv(i):
// *receiver_count(i)++
//
@@ -2208,7 +2207,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
sub(rscratch1, offset, end_receiver_offset);
cbnz(rscratch1, L_loop_search_receiver);
- // Fast: no receiver, but profile is full
+ // Fast: no receiver, but profile is not full
mov(offset, base_receiver_offset);
bind(L_loop_search_empty);
ldr(rscratch1, Address(mdp, offset));
@@ -2216,9 +2215,13 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
add(offset, offset, receiver_step);
sub(rscratch1, offset, end_receiver_offset);
cbnz(rscratch1, L_loop_search_empty);
- b(L_polymorphic);
- // Slow: try to install receiver
+ // Slow: Receiver is not found and table is full.
+ // Increment polymorphic counter instead of receiver slot.
+ mov(offset, poly_count_offset);
+ b(L_count_update);
+
+ // Slowest: try to install receiver
bind(L_found_empty);
// Atomically swing receiver slot: null -> recv.
@@ -2237,17 +2240,11 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
// and just restart the search from the beginning.
b(L_restart);
- // Counter updates:
-
- // Increment polymorphic counter instead of receiver slot.
- bind(L_polymorphic);
- mov(offset, poly_count_offset);
- b(L_count_update);
-
// Found a receiver, convert its slot offset to corresponding count offset.
bind(L_found_recv);
add(offset, offset, receiver_to_count_step);
+ // Finally, update the counter
bind(L_count_update);
increment(Address(mdp, offset), DataLayout::counter_increment);
}
@@ -2816,7 +2813,7 @@ void MacroAssembler::decrementw(Register reg, int value)
{
if (value < 0) { incrementw(reg, -value); return; }
if (value == 0) { return; }
- if (value < (1 << 12)) { subw(reg, reg, value); return; }
+ if (value < (1 << 24)) { subw(reg, reg, value); return; }
/* else */ {
guarantee(reg != rscratch2, "invalid dst for register decrement");
movw(rscratch2, (unsigned)value);
@@ -2828,7 +2825,7 @@ void MacroAssembler::decrement(Register reg, int value)
{
if (value < 0) { increment(reg, -value); return; }
if (value == 0) { return; }
- if (value < (1 << 12)) { sub(reg, reg, value); return; }
+ if (value < (1 << 24)) { sub(reg, reg, value); return; }
/* else */ {
assert(reg != rscratch2, "invalid dst for register decrement");
mov(rscratch2, (uint64_t)value);
@@ -2840,7 +2837,7 @@ void MacroAssembler::decrementw(Address dst, int value)
{
assert(!dst.uses(rscratch1), "invalid dst for address decrement");
if (dst.getMode() == Address::literal) {
- assert(abs(value) < (1 << 12), "invalid value and address mode combination");
+ assert(abs(value) < (1 << 24), "invalid value and address mode combination");
lea(rscratch2, dst);
dst = Address(rscratch2);
}
@@ -2853,7 +2850,7 @@ void MacroAssembler::decrement(Address dst, int value)
{
assert(!dst.uses(rscratch1), "invalid address for decrement");
if (dst.getMode() == Address::literal) {
- assert(abs(value) < (1 << 12), "invalid value and address mode combination");
+ assert(abs(value) < (1 << 24), "invalid value and address mode combination");
lea(rscratch2, dst);
dst = Address(rscratch2);
}
@@ -2866,7 +2863,7 @@ void MacroAssembler::incrementw(Register reg, int value)
{
if (value < 0) { decrementw(reg, -value); return; }
if (value == 0) { return; }
- if (value < (1 << 12)) { addw(reg, reg, value); return; }
+ if (value < (1 << 24)) { addw(reg, reg, value); return; }
/* else */ {
assert(reg != rscratch2, "invalid dst for register increment");
movw(rscratch2, (unsigned)value);
@@ -2878,7 +2875,7 @@ void MacroAssembler::increment(Register reg, int value)
{
if (value < 0) { decrement(reg, -value); return; }
if (value == 0) { return; }
- if (value < (1 << 12)) { add(reg, reg, value); return; }
+ if (value < (1 << 24)) { add(reg, reg, value); return; }
/* else */ {
assert(reg != rscratch2, "invalid dst for register increment");
movw(rscratch2, (unsigned)value);
@@ -2886,30 +2883,34 @@ void MacroAssembler::increment(Register reg, int value)
}
}
-void MacroAssembler::incrementw(Address dst, int value)
+void MacroAssembler::incrementw(Address dst, int value, Register result)
{
- assert(!dst.uses(rscratch1), "invalid dst for address increment");
+ assert(!dst.uses(result), "invalid dst for address increment");
+ assert(result->is_valid(), "must be");
+ assert_different_registers(result, rscratch2);
if (dst.getMode() == Address::literal) {
- assert(abs(value) < (1 << 12), "invalid value and address mode combination");
+ assert(abs(value) < (1 << 24), "invalid value and address mode combination");
lea(rscratch2, dst);
dst = Address(rscratch2);
}
- ldrw(rscratch1, dst);
- incrementw(rscratch1, value);
- strw(rscratch1, dst);
+ ldrw(result, dst);
+ incrementw(result, value);
+ strw(result, dst);
}
-void MacroAssembler::increment(Address dst, int value)
+void MacroAssembler::increment(Address dst, int value, Register result)
{
- assert(!dst.uses(rscratch1), "invalid dst for address increment");
+ assert(!dst.uses(result), "invalid dst for address increment");
+ assert(result->is_valid(), "must be");
+ assert_different_registers(result, rscratch2);
if (dst.getMode() == Address::literal) {
- assert(abs(value) < (1 << 12), "invalid value and address mode combination");
+ assert(abs(value) < (1 << 24), "invalid value and address mode combination");
lea(rscratch2, dst);
dst = Address(rscratch2);
}
- ldr(rscratch1, dst);
- increment(rscratch1, value);
- str(rscratch1, dst);
+ ldr(result, dst);
+ increment(result, value);
+ str(result, dst);
}
// Push lots of registers in the bit set supplied. Don't push sp.
@@ -7278,3 +7279,26 @@ void MacroAssembler::neon_vector_rotate(FloatRegister dst, SIMD_Arrangement T,
sli(dst, T, src, lshift);
}
}
+
+void MacroAssembler::try_to_replace_prev_vector_copy_with_movprfx(FloatRegister dst) {
+ if (code_section()->is_empty()) {
+ return;
+ }
+
+ address prev = pc() - NativeInstruction::instruction_size;
+ uint32_t insn = nativeInstruction_at(prev)->encoding();
+ if (!NativeInstruction::is_neon_vector_mov_alias(insn) &&
+ !NativeInstruction::is_sve_vector_mov_alias(insn)) {
+ return;
+ }
+
+ // The destructive instruction must reuse the mov alias destination.
+ uint32_t rd = Instruction_aarch64::extract(insn, 4, 0);
+ if (rd != (uint32_t)dst->encoding()) {
+ return;
+ }
+
+ uint32_t rn = Instruction_aarch64::extract(insn, 9, 5);
+ Instruction_aarch64::patch(prev, 31, 0,
+ NativeInstruction::encode_sve_movprfx(rd, rn));
+}
diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp
index ad8827bd9c0..a15b0630610 100644
--- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp
@@ -482,6 +482,25 @@ class MacroAssembler: public Assembler {
WRAP(smaddl) WRAP(smsubl) WRAP(umaddl) WRAP(umsubl)
#undef WRAP
+ using Assembler::andw, Assembler::andr;
+ void andw(Register Rd, Register Rn, uint64_t imm) {
+ if (operand_valid_for_logical_immediate(/*is32*/true, imm)) {
+ Assembler::andw(Rd, Rn, imm);
+ } else {
+ assert(Rd != Rn, "must be");
+ movw(Rd, imm);
+ andw(Rd, Rn, Rd);
+ }
+ }
+ void andr(Register Rd, Register Rn, uint64_t imm) {
+ if (operand_valid_for_logical_immediate(/*is32*/false, imm)) {
+ Assembler::andr(Rd, Rn, imm);
+ } else {
+ assert(Rd != Rn, "must be");
+ mov(Rd, imm);
+ andr(Rd, Rn, Rd);
+ }
+ }
// macro assembly operations needed for aarch64
@@ -743,7 +762,7 @@ public:
// n.b. increment/decrement calls with an Address destination will
// need to use a scratch register to load the value to be
// incremented. increment/decrement calls which add or subtract a
- // constant value greater than 2^12 will need to use a 2nd scratch
+ // constant value greater than 2^24 will need to use a 2nd scratch
// register to hold the constant. so, a register increment/decrement
// may trash rscratch2 and an address increment/decrement trash
// rscratch and rscratch2
@@ -754,11 +773,11 @@ public:
void decrement(Register reg, int value = 1);
void decrement(Address dst, int value = 1);
- void incrementw(Address dst, int value = 1);
+ void incrementw(Address dst, int value = 1, Register result = rscratch1);
void incrementw(Register reg, int value = 1);
void increment(Register reg, int value = 1);
- void increment(Address dst, int value = 1);
+ void increment(Address dst, int value = 1, Register result = rscratch1);
// Alignment
@@ -1734,7 +1753,103 @@ public:
private:
// Check the current thread doesn't need a cross modify fence.
void verify_cross_modify_fence_not_required() PRODUCT_RETURN;
+ void try_to_replace_prev_vector_copy_with_movprfx(FloatRegister dst);
+public:
+ void maybe_movprfx(FloatRegister dst, FloatRegister src) {
+ if (dst != src) {
+ sve_movprfx(dst, src);
+ }
+ }
+
+// Wrappers for SVE explicit destructive instructions, overriding the
+// same-signature Assembler entry points to enable movprfx fusion optimization.
+//
+// Implicit destructive instructions (e.g. predicated unary ops like sve_abs/
+// sve_neg/sve_not, whose ISA encoding allows Zd != Zn but whose use as a Java
+// Vector API masked operation requires pass-through of the first source) are
+// not covered here. For those, the .ad file is responsible for emitting
+// movprfx explicitly via maybe_movprfx() before the destructive op.
+#define SVE_DESTRUCTIVE_BINARY_INS(NAME) \
+ using Assembler::NAME; \
+ void NAME(FloatRegister Zd, SIMD_RegVariant T, PRegister Pg, \
+ FloatRegister Zm) { \
+ if (Zd != Zm) { \
+ try_to_replace_prev_vector_copy_with_movprfx(Zd); \
+ } \
+ Assembler::NAME(Zd, T, Pg, Zm); \
+ }
+
+#define SVE_DESTRUCTIVE_BINARY_5(I1, I2, I3, I4, I5) \
+ SVE_DESTRUCTIVE_BINARY_INS(I1); SVE_DESTRUCTIVE_BINARY_INS(I2); \
+ SVE_DESTRUCTIVE_BINARY_INS(I3); SVE_DESTRUCTIVE_BINARY_INS(I4); \
+ SVE_DESTRUCTIVE_BINARY_INS(I5);
+
+ SVE_DESTRUCTIVE_BINARY_5(sve_add, sve_and, sve_asr, sve_bic, sve_eor)
+ SVE_DESTRUCTIVE_BINARY_5(sve_fabd, sve_fadd, sve_fdiv, sve_fmax, sve_fmin)
+ SVE_DESTRUCTIVE_BINARY_5(sve_fmul, sve_fsub, sve_lsl, sve_lsr, sve_mul)
+ SVE_DESTRUCTIVE_BINARY_5(sve_orr, sve_smax, sve_smin, sve_sqadd, sve_sqsub)
+ SVE_DESTRUCTIVE_BINARY_5(sve_sub, sve_uqadd, sve_uqsub, sve_umax, sve_umin)
+
+#undef SVE_DESTRUCTIVE_BINARY_INS
+#undef SVE_DESTRUCTIVE_BINARY_5
+
+#define SVE_DESTRUCTIVE_SHIFT_IMM_INS(NAME) \
+ void NAME(FloatRegister Zd, SIMD_RegVariant T, PRegister Pg, int shift) { \
+ try_to_replace_prev_vector_copy_with_movprfx(Zd); \
+ Assembler::NAME(Zd, T, Pg, shift); \
+ }
+
+ SVE_DESTRUCTIVE_SHIFT_IMM_INS(sve_asr);
+ SVE_DESTRUCTIVE_SHIFT_IMM_INS(sve_lsl);
+ SVE_DESTRUCTIVE_SHIFT_IMM_INS(sve_lsr);
+
+#undef SVE_DESTRUCTIVE_SHIFT_IMM_INS
+
+#define SVE_DESTRUCTIVE_UNPRED_IMM_INS(NAME, IMM_TYPE) \
+ void NAME(FloatRegister Zd, SIMD_RegVariant T, IMM_TYPE imm) { \
+ try_to_replace_prev_vector_copy_with_movprfx(Zd); \
+ Assembler::NAME(Zd, T, imm); \
+ }
+
+ SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_add, unsigned);
+ SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_sub, unsigned);
+ SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_and, uint64_t);
+ SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_eor, uint64_t);
+ SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_orr, uint64_t);
+
+#undef SVE_DESTRUCTIVE_UNPRED_IMM_INS
+
+#define SVE_DESTRUCTIVE_TERNARY_INS(NAME) \
+ using Assembler::NAME; \
+ void NAME(FloatRegister Zd, SIMD_RegVariant T, PRegister Pg, \
+ FloatRegister Zn, FloatRegister Zm) { \
+ if (Zd != Zn && Zd != Zm) { \
+ try_to_replace_prev_vector_copy_with_movprfx(Zd); \
+ } \
+ Assembler::NAME(Zd, T, Pg, Zn, Zm); \
+ }
+
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fmad);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fmla);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fmls);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fmsb);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmad);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmla);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmls);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmsb);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_mla);
+ SVE_DESTRUCTIVE_TERNARY_INS(sve_mls);
+
+#undef SVE_DESTRUCTIVE_TERNARY_INS
+
+ using Assembler::sve_eor3;
+ void sve_eor3(FloatRegister Zd, FloatRegister Zm, FloatRegister Zk) {
+ if (Zd != Zm && Zd != Zk) {
+ try_to_replace_prev_vector_copy_with_movprfx(Zd);
+ }
+ Assembler::sve_eor3(Zd, Zm, Zk);
+ }
};
#ifdef ASSERT
diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp
index 4bccbc59582..57bb9a91533 100644
--- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp
@@ -140,6 +140,29 @@ public:
Instruction_aarch64::extract(insn, 23, 23) == 0b0 &&
Instruction_aarch64::extract(insn, 26, 25) == 0b00;
}
+
+ static bool is_neon_vector_mov_alias(uint32_t insn) {
+ if (Instruction_aarch64::extract(insn, 31, 31) != 0 ||
+ Instruction_aarch64::extract(insn, 29, 21) != 0b001110101 ||
+ Instruction_aarch64::extract(insn, 15, 10) != 0b000111) {
+ return false;
+ }
+ return Instruction_aarch64::extract(insn, 9, 5) ==
+ Instruction_aarch64::extract(insn, 20, 16);
+ }
+
+ static bool is_sve_vector_mov_alias(uint32_t insn) {
+ if (Instruction_aarch64::extract(insn, 31, 21) != 0b00000100011 ||
+ Instruction_aarch64::extract(insn, 15, 10) != 0b001100) {
+ return false;
+ }
+ return Instruction_aarch64::extract(insn, 9, 5) ==
+ Instruction_aarch64::extract(insn, 20, 16);
+ }
+
+ static uint32_t encode_sve_movprfx(uint32_t dst, uint32_t src) {
+ return 0x1082f << 10 | (src << 5) | dst;
+ }
};
inline NativeInstruction* nativeInstruction_at(address address) {
diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
index 46849b329cb..8e9af2b7b8a 100644
--- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
@@ -6276,6 +6276,24 @@ class StubGenerator: public StubCodeGenerator {
// static int implKyberNttMult(
// short[] result, short[] ntta, short[] nttb, short[] zetas) {}
//
+ // The actual algorithm that is used here differs from the one in the Java
+ // implementation, it uses Montgomery multiplications instead of Barrett
+ // reduction, but the end result modulo MLKEM_Q is the same. This is the
+ // Java equivalent of this intrinsic implementation:
+ // static void implKyberNttMultJava(short[] result, short[] ntta, short[] nttb) {
+ // for (int m = 0; m < ML_KEM_N / 2; m++) {
+ // int a0 = ntta[2 * m];
+ // int a1 = ntta[2 * m + 1];
+ // int b0 = nttb[2 * m];
+ // int b1 = nttb[2 * m + 1];
+ // int r = montMul(a0, b0) +
+ // montMul(montMul(a1, b1), MONT_ZETAS_FOR_NTT_MULT[m]);
+ // result[2 * m] = (short) montMul(r, MONT_R_SQUARE_MOD_Q);
+ // result[2 * m + 1] = (short) montMul(
+ // (montMul(a0, b1) + montMul(a1, b0)), MONT_R_SQUARE_MOD_Q);
+ // }
+ // }
+ //
// result (short[256]) = c_rarg0
// ntta (short[256]) = c_rarg1
// nttb (short[256]) = c_rarg2
diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
index a9f34b148c6..1270471d150 100644
--- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
@@ -2226,39 +2226,12 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) {
}
+// kills recv
void LIR_Assembler::type_profile_helper(Register mdo, int mdo_offset_bias,
ciMethodData *md, ciProfileData *data,
- Register recv, Register tmp1, Label* update_done) {
- uint i;
- for (i = 0; i < VirtualCallData::row_limit(); i++) {
- Label next_test;
- // See if the receiver is receiver[n].
- __ ld(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i)) - mdo_offset_bias, mdo);
- __ verify_klass_ptr(tmp1);
- __ cmpd(CR0, recv, tmp1);
- __ bne(CR0, next_test);
-
- __ ld(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i)) - mdo_offset_bias, mdo);
- __ addi(tmp1, tmp1, DataLayout::counter_increment);
- __ std(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i)) - mdo_offset_bias, mdo);
- __ b(*update_done);
-
- __ bind(next_test);
- }
-
- // Didn't find receiver; find next empty slot and fill it in.
- for (i = 0; i < VirtualCallData::row_limit(); i++) {
- Label next_test;
- __ ld(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i)) - mdo_offset_bias, mdo);
- __ cmpdi(CR0, tmp1, 0);
- __ bne(CR0, next_test);
- __ li(tmp1, DataLayout::counter_increment);
- __ std(recv, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i)) - mdo_offset_bias, mdo);
- __ std(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i)) - mdo_offset_bias, mdo);
- __ b(*update_done);
-
- __ bind(next_test);
- }
+ Register recv, Register tmp) {
+ int mdp_offset = md->byte_offset_of_slot(data, in_ByteSize(0)) - mdo_offset_bias;
+ __ profile_receiver_type(recv, mdo, mdp_offset, tmp, noreg);
}
@@ -2320,15 +2293,9 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L
__ b(*obj_is_null);
__ bind(not_null);
- Label update_done;
Register recv = klass_RInfo;
__ load_klass(recv, obj);
- type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1, &update_done);
- const int slot_offset = md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias;
- __ ld(Rtmp1, slot_offset, mdo);
- __ addi(Rtmp1, Rtmp1, DataLayout::counter_increment);
- __ std(Rtmp1, slot_offset, mdo);
- __ bind(update_done);
+ type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1); // kills recv
} else {
__ cmpdi(CR0, obj, 0);
__ beq(CR0, *obj_is_null);
@@ -2427,15 +2394,9 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) {
__ b(done);
__ bind(not_null);
- Label update_done;
Register recv = klass_RInfo;
__ load_klass(recv, value);
- type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1, &update_done);
- const int slot_offset = md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias;
- __ ld(Rtmp1, slot_offset, mdo);
- __ addi(Rtmp1, Rtmp1, DataLayout::counter_increment);
- __ std(Rtmp1, slot_offset, mdo);
- __ bind(update_done);
+ type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1); // kills recv
} else {
__ cmpdi(CR0, value, 0);
__ beq(CR0, done);
@@ -2648,55 +2609,27 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) {
// We know the type that will be seen at this call site; we can
// statically update the MethodData* rather than needing to do
// dynamic tests on the receiver type.
-
- // NOTE: we should probably put a lock around this search to
- // avoid collisions by concurrent compilations.
ciVirtualCallData* vc_data = (ciVirtualCallData*) data;
- uint i;
- for (i = 0; i < VirtualCallData::row_limit(); i++) {
+ for (uint i = 0; i < VirtualCallData::row_limit(); i++) {
ciKlass* receiver = vc_data->receiver(i);
if (known_klass->equals(receiver)) {
- __ ld(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo);
- __ addi(tmp1, tmp1, DataLayout::counter_increment);
- __ std(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo);
+ __ increment_mem64(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias,
+ DataLayout::counter_increment, tmp1);
return;
}
}
- // Receiver type not found in profile data; select an empty slot.
-
- // Note that this is less efficient than it should be because it
- // always does a write to the receiver part of the
- // VirtualCallData rather than just the first time.
- for (i = 0; i < VirtualCallData::row_limit(); i++) {
- ciKlass* receiver = vc_data->receiver(i);
- if (receiver == nullptr) {
- metadata2reg(known_klass->constant_encoding(), tmp1);
- __ std(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_offset(i)) - mdo_offset_bias, mdo);
-
- __ ld(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo);
- __ addi(tmp1, tmp1, DataLayout::counter_increment);
- __ std(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo);
- return;
- }
- }
+ // Receiver type is not found in profile data.
+ // Fall back to runtime helper to handle the rest at runtime.
+ metadata2reg(known_klass->constant_encoding(), recv);
} else {
__ load_klass(recv, recv);
- Label update_done;
- type_profile_helper(mdo, mdo_offset_bias, md, data, recv, tmp1, &update_done);
- // Receiver did not match any saved receiver and there is no empty row for it.
- // Increment total counter to indicate polymorphic case.
- __ ld(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo);
- __ addi(tmp1, tmp1, DataLayout::counter_increment);
- __ std(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo);
-
- __ bind(update_done);
}
+ type_profile_helper(mdo, mdo_offset_bias, md, data, recv, tmp1); // kills recv
} else {
// Static call
- __ ld(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo);
- __ addi(tmp1, tmp1, DataLayout::counter_increment);
- __ std(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo);
+ __ increment_mem64(mdo, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias,
+ DataLayout::counter_increment, tmp1);
}
}
diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp
index 7399a4544e6..5a065d364b2 100644
--- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp
@@ -52,7 +52,7 @@ friend class ArrayCopyStub;
// Record the type of the receiver in ReceiverTypeData.
void type_profile_helper(Register mdo, int mdo_offset_bias,
ciMethodData *md, ciProfileData *data,
- Register recv, Register tmp1, Label* update_done);
+ Register recv, Register tmp);
// Setup pointers to MDO, MDO slot, also compute offset bias to access the slot.
void setup_md_access(ciMethod* method, int bci,
ciMethodData*& md, ciProfileData*& data, int& mdo_offset_bias);
diff --git a/src/hotspot/cpu/ppc/frame_ppc.cpp b/src/hotspot/cpu/ppc/frame_ppc.cpp
index 6b6a792117d..7d2e22b5965 100644
--- a/src/hotspot/cpu/ppc/frame_ppc.cpp
+++ b/src/hotspot/cpu/ppc/frame_ppc.cpp
@@ -1,6 +1,6 @@
/*
- * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012, 2025 SAP SE. All rights reserved.
+ * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2026 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -319,7 +319,7 @@ void frame::patch_pc(Thread* thread, address pc) {
#ifdef ASSERT
{
- frame f(this->sp(), pc, this->unextended_sp());
+ frame f(sp(), unextended_sp(), fp(), pc, cb(), oop_map(), is_heap_frame());
assert(f.is_deoptimized_frame() == this->is_deoptimized_frame() && f.pc() == this->pc() && f.raw_pc() == this->raw_pc(),
"must be (f.is_deoptimized_frame(): %d this->is_deoptimized_frame(): %d "
"f.pc(): " INTPTR_FORMAT " this->pc(): " INTPTR_FORMAT " f.raw_pc(): " INTPTR_FORMAT " this->raw_pc(): " INTPTR_FORMAT ")",
diff --git a/src/hotspot/cpu/ppc/frame_ppc.inline.hpp b/src/hotspot/cpu/ppc/frame_ppc.inline.hpp
index cedcb399a83..123e6d8a0b1 100644
--- a/src/hotspot/cpu/ppc/frame_ppc.inline.hpp
+++ b/src/hotspot/cpu/ppc/frame_ppc.inline.hpp
@@ -81,7 +81,7 @@ inline void frame::setup(kind knd) {
// Continuation frames on the java heap are not aligned.
// When thawing interpreted frames the sp can be unaligned (see new_stack_frame()).
assert(_on_heap ||
- ((is_aligned(_sp, alignment_in_bytes) || is_interpreted_frame() || is_deoptimized_frame()) &&
+ ((is_aligned(_sp, alignment_in_bytes) || is_interpreted_frame()) &&
(is_aligned(_fp, alignment_in_bytes) || !is_fully_initialized())),
"invalid alignment sp:" PTR_FORMAT " unextended_sp:" PTR_FORMAT " fp:" PTR_FORMAT, p2i(_sp), p2i(_unextended_sp), p2i(_fp));
}
diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp
index 9c079107e08..582327282fd 100644
--- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp
@@ -31,7 +31,6 @@
#include "gc/shenandoah/mode/shenandoahMode.hpp"
#include "gc/shenandoah/shenandoahBarrierSet.hpp"
#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp"
-#include "gc/shenandoah/shenandoahForwarding.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
@@ -335,43 +334,6 @@ void ShenandoahBarrierSetAssembler::satb_barrier_impl(MacroAssembler *masm, Deco
__ bind(skip_barrier);
}
-void ShenandoahBarrierSetAssembler::resolve_forward_pointer_not_null(MacroAssembler *masm, Register dst, Register tmp) {
- __ block_comment("resolve_forward_pointer_not_null (shenandoahgc) {");
-
- Register tmp1 = tmp,
- R0_tmp2 = R0;
- assert_different_registers(dst, tmp1, R0_tmp2, noreg);
-
- // If the object has been evacuated, the mark word layout is as follows:
- // | forwarding pointer (62-bit) | '11' (2-bit) |
-
- // The invariant that stack/thread pointers have the lowest two bits cleared permits retrieving
- // the forwarding pointer solely by inversing the lowest two bits.
- // This invariant follows inevitably from hotspot's minimal alignment.
- assert(markWord::marked_value <= (unsigned long) MinObjAlignmentInBytes,
- "marked value must not be higher than hotspot's minimal alignment");
-
- Label done;
-
- // Load the object's mark word.
- __ ld(tmp1, oopDesc::mark_offset_in_bytes(), dst);
-
- // Load the bit mask for the lock bits.
- __ li(R0_tmp2, markWord::lock_mask_in_place);
-
- // Check whether all bits matching the bit mask are set.
- // If that is the case, the object has been evacuated and the most significant bits form the forward pointer.
- __ andc_(R0_tmp2, R0_tmp2, tmp1);
-
- assert(markWord::lock_mask_in_place == markWord::marked_value,
- "marked value must equal the value obtained when all lock bits are being set");
- __ xori(tmp1, tmp1, markWord::lock_mask_in_place);
- __ isel(dst, CR0, Assembler::equal, false, tmp1);
-
- __ bind(done);
- __ block_comment("} resolve_forward_pointer_not_null (shenandoahgc)");
-}
-
// base: Base register of the reference's address.
// ind_or_offs: Index or offset of the reference's address (load mode).
// dst: Reference's address. In case the object has been evacuated, this is the to-space version
@@ -693,125 +655,6 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl
__ block_comment("} try_peek_weak_handle_in_nmethod (shenandoahgc)");
}
-// Special shenandoah CAS implementation that handles false negatives due
-// to concurrent evacuation. That is, the CAS operation is intended to succeed in
-// the following scenarios (success criteria):
-// s1) The reference pointer ('base_addr') equals the expected ('expected') pointer.
-// s2) The reference pointer refers to the from-space version of an already-evacuated
-// object, whereas the expected pointer refers to the to-space version of the same object.
-// Situations in which the reference pointer refers to the to-space version of an object
-// and the expected pointer refers to the from-space version of the same object can not occur due to
-// shenandoah's strong to-space invariant. This also implies that the reference stored in 'new_val'
-// can not refer to the from-space version of an already-evacuated object.
-//
-// To guarantee correct behavior in concurrent environments, two races must be addressed:
-// r1) A concurrent thread may heal the reference pointer (i.e., it is no longer referring to the
-// from-space version but to the to-space version of the object in question).
-// In this case, the CAS operation should succeed.
-// r2) A concurrent thread may mutate the reference (i.e., the reference pointer refers to an entirely different object).
-// In this case, the CAS operation should fail.
-//
-// By default, the value held in the 'result' register is zero to indicate failure of CAS,
-// non-zero to indicate success. If 'is_cae' is set, the result is the most recently fetched
-// value from 'base_addr' rather than a boolean success indicator.
-void ShenandoahBarrierSetAssembler::cmpxchg_oop(MacroAssembler *masm, Register base_addr,
- Register expected, Register new_val, Register tmp1, Register tmp2,
- bool is_cae, Register result) {
- __ block_comment("cmpxchg_oop (shenandoahgc) {");
-
- assert_different_registers(base_addr, new_val, tmp1, tmp2, result, R0);
- assert_different_registers(base_addr, expected, tmp1, tmp2, result, R0);
-
- // Potential clash of 'success_flag' and 'tmp' is being accounted for.
- Register success_flag = is_cae ? noreg : result,
- current_value = is_cae ? result : tmp1,
- tmp = is_cae ? tmp1 : result,
- initial_value = tmp2;
-
- Label done, step_four;
-
- __ bind(step_four);
-
- /* ==== Step 1 ("Standard" CAS) ==== */
- // Fast path: The values stored in 'expected' and 'base_addr' are equal.
- // Given that 'expected' must refer to the to-space object of an evacuated object (strong to-space invariant),
- // no special processing is required.
- if (UseCompressedOops) {
- __ cmpxchgw(CR0, current_value, expected, new_val, base_addr, MacroAssembler::MemBarNone,
- false, success_flag, nullptr, true);
- } else {
- __ cmpxchgd(CR0, current_value, expected, new_val, base_addr, MacroAssembler::MemBarNone,
- false, success_flag, nullptr, true);
- }
-
- // Skip the rest of the barrier if the CAS operation succeeds immediately.
- // If it does not, the value stored at the address is either the from-space pointer of the
- // referenced object (success criteria s2)) or simply another object.
- __ beq(CR0, done);
-
- /* ==== Step 2 (Null check) ==== */
- // The success criteria s2) cannot be matched with a null pointer
- // (null pointers cannot be subject to concurrent evacuation). The failure of the CAS operation is thus legitimate.
- __ cmpdi(CR0, current_value, 0);
- __ beq(CR0, done);
-
- /* ==== Step 3 (reference pointer refers to from-space version; success criteria s2)) ==== */
- // To check whether the reference pointer refers to the from-space version, the forward
- // pointer of the object referred to by the reference is resolved and compared against the expected pointer.
- // If this check succeed, another CAS operation is issued with the from-space pointer being the expected pointer.
- //
- // Save the potential from-space pointer.
- __ mr(initial_value, current_value);
-
- // Resolve forward pointer.
- if (UseCompressedOops) { __ decode_heap_oop_not_null(current_value); }
- resolve_forward_pointer_not_null(masm, current_value, tmp);
- if (UseCompressedOops) { __ encode_heap_oop_not_null(current_value); }
-
- if (!is_cae) {
- // 'success_flag' was overwritten by call to 'resovle_forward_pointer_not_null'.
- // Load zero into register for the potential failure case.
- __ li(success_flag, 0);
- }
- __ cmpd(CR0, current_value, expected);
- __ bne(CR0, done);
-
- // Discard fetched value as it might be a reference to the from-space version of an object.
- if (UseCompressedOops) {
- __ cmpxchgw(CR0, R0, initial_value, new_val, base_addr, MacroAssembler::MemBarNone,
- false, success_flag);
- } else {
- __ cmpxchgd(CR0, R0, initial_value, new_val, base_addr, MacroAssembler::MemBarNone,
- false, success_flag);
- }
-
- /* ==== Step 4 (Retry CAS with to-space pointer (success criteria s2) under race r1)) ==== */
- // The reference pointer could have been healed whilst the previous CAS operation was being performed.
- // Another CAS operation must thus be issued with the to-space pointer being the expected pointer.
- // If that CAS operation fails as well, race r2) must have occurred, indicating that
- // the operation failure is legitimate.
- //
- // To keep the code's size small and thus improving cache (icache) performance, this highly
- // unlikely case should be handled by the smallest possible code. Instead of emitting a third,
- // explicit CAS operation, the code jumps back and reuses the first CAS operation (step 1)
- // (passed arguments are identical).
- //
- // A failure of the CAS operation in step 1 would imply that the overall CAS operation is supposed
- // to fail. Jumping back to step 1 requires, however, that step 2 and step 3 are re-executed as well.
- // It is thus important to ensure that a re-execution of those steps does not put program correctness
- // at risk:
- // - Step 2: Either terminates in failure (desired result) or falls through to step 3.
- // - Step 3: Terminates if the comparison between the forwarded, fetched pointer and the expected value
- // fails. Unless the reference has been updated in the meanwhile once again, this is
- // guaranteed to be the case.
- // In case of a concurrent update, the CAS would be retried again. This is legitimate
- // in terms of program correctness (even though it is not desired).
- __ bne(CR0, step_four);
-
- __ bind(done);
- __ block_comment("} cmpxchg_oop (shenandoahgc)");
-}
-
void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
Register addr, Register count, Register preserve) {
assert(ShenandoahCardBarrier, "Should have been checked by caller");
@@ -1115,7 +958,7 @@ void ShenandoahBarrierSetAssembler::load_c2(const MachNode* node, MacroAssembler
void ShenandoahBarrierSetAssembler::store_c2(const MachNode* node, MacroAssembler* masm,
Register dst, int disp, bool dst_narrow, Register src, bool src_narrow, Register tmp1, Register tmp2, Register tmp3) {
- ShenandoahBarrierStubC2::store_pre(masm, node, tmp1, Address(dst, disp), tmp2, tmp3, dst_narrow);
+ ShenandoahBarrierStubC2::store_pre(masm, node, Address(dst, disp), tmp1, tmp2, tmp3, dst_narrow);
if (dst_narrow && !src_narrow) {
// Need to encode into tmp, because we cannot clobber src.
@@ -1134,39 +977,41 @@ void ShenandoahBarrierSetAssembler::store_c2(const MachNode* node, MacroAssemble
ShenandoahBarrierStubC2::store_post(masm, node, Address(dst, disp), tmp1, tmp2);
}
-void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr, Register oldval,
- Register newval, Register tmp1, Register tmp2, Register tmp3, bool exchange, bool narrow, bool weak, bool acquire) {
+void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr,
+ Register oldval, Register newval, Register tmp1, Register tmp2, bool exchange, bool narrow, bool weak, bool acquire) {
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, addr, tmp2, tmp3, narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, res, tmp1, tmp2, narrow);
- Register dest_current = exchange ? res : R0;
- Register int_flag = exchange ? noreg : res;
- int semantics = MacroAssembler::MemBarNone;
+ Register dest_current = exchange ? res : R0;
+ Label no_update;
+ int semantics = MacroAssembler::MemBarNone;
if (acquire) {
semantics = support_IRIW_for_not_multiple_copy_atomic_cpu ?
MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter;
}
+ if (!exchange) { __ li(res, 0); }
if (narrow) {
- // CmpxchgX sets CR0 to cmpX(src1, src2) and Rres to 'true'/'false'.
__ cmpxchgw(CR0, dest_current, oldval, newval, addr,
semantics, MacroAssembler::cmpxchgx_hint_atomic_update(),
- int_flag, nullptr, true, weak);
+ noreg, &no_update, true, weak);
} else {
- // CmpxchgX sets CR0 to cmpX(src1, src2) and Rres to 'true'/'false'.
__ cmpxchgd(CR0, dest_current, oldval, newval, addr,
semantics, MacroAssembler::cmpxchgx_hint_atomic_update(),
- int_flag, nullptr, true, weak);
+ noreg, &no_update, true, weak);
}
+ if (!exchange) { __ li(res, 1); }
ShenandoahBarrierStubC2::load_store_post(masm, node, Address(addr, 0), tmp1, tmp2);
+
+ __ bind(no_update);
}
-void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAssembler* masm, Register preval, Register newval, Register addr, Register tmp1, Register tmp2, Register tmp3) {
+void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAssembler* masm, Register preval, Register newval, Register addr, Register tmp1, Register tmp2) {
bool is_narrow = node->bottom_type()->isa_narrowoop();
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, addr, tmp2, tmp3, is_narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, preval, tmp1, tmp2, is_narrow);
if (is_narrow) {
__ getandsetw(preval, newval, addr, MacroAssembler::cmpxchgx_hint_atomic_update());
@@ -1365,12 +1210,9 @@ void ShenandoahBarrierStubC2::lrb(MacroAssembler& masm) {
if (c_rarg0 == _obj) {
__ addi(c_rarg1, _addr.base(), _addr.disp());
} else if (c_rarg1 == _obj) {
- // Set up arguments in reverse, and then flip them
- __ addi(c_rarg0, _addr.base(), _addr.disp());
- // flip them
- __ mr(_tmp1, c_rarg0);
- __ mr(c_rarg0, c_rarg1);
- __ mr(c_rarg1, _tmp1);
+ __ mr(_tmp1, c_rarg1);
+ __ addi(c_rarg1, _addr.base(), _addr.disp());
+ __ mr(c_rarg0, _tmp1);
} else {
assert_different_registers(c_rarg1, _obj);
__ addi(c_rarg1, _addr.base(), _addr.disp());
diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp
index eda3aafdb2d..bd1043c2d76 100644
--- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp
@@ -69,8 +69,6 @@ private:
MacroAssembler::PreservationLevel preservation_level);
/* ==== Helper methods for barrier implementations ==== */
- void resolve_forward_pointer_not_null(MacroAssembler* masm, Register dst, Register tmp);
-
void gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
Register addr, Register count,
Register preserve);
@@ -103,11 +101,6 @@ public:
Register tmp1, Register tmp2,
MacroAssembler::PreservationLevel preservation_level);
- /* ==== Helper methods used by C1 and C2 ==== */
- void cmpxchg_oop(MacroAssembler* masm, Register base_addr, Register expected, Register new_val,
- Register tmp1, Register tmp2,
- bool is_cae, Register result);
-
/* ==== Access api ==== */
virtual void arraycopy_prologue(MacroAssembler* masm, DecoratorSet decorators, BasicType type,
Register src, Register dst, Register count,
@@ -140,10 +133,10 @@ public:
Register dst, int disp, bool dst_narrow, Register src, bool src_narrow, Register tmp1, Register tmp2, Register tmp3);
void compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr, Register oldval,
- Register newval, Register tmp1, Register tmp2, Register tmp3, bool exchange, bool narrow, bool weak, bool acquire);
+ Register newval, Register tmp1, Register tmp2, bool exchange, bool narrow, bool weak, bool acquire);
void get_and_set_c2(const MachNode* node, MacroAssembler* masm,
- Register preval, Register newval, Register addr, Register tmp1, Register tmp2, Register tmp3);
+ Register preval, Register newval, Register addr, Register tmp1, Register tmp2);
#endif // COMPILER2
};
diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad
index a75d1a03e4b..f2e5d4f3a27 100644
--- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad
+++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad
@@ -1,6 +1,6 @@
//
// Copyright (c) 2018, 2021, Red Hat, Inc. All rights reserved.
-// Copyright (c) 2012, 2021 SAP SE. All rights reserved.
+// Copyright (c) 2012, 2026 SAP SE. All rights reserved.
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
//
// This code is free software; you can redistribute it and/or modify it
@@ -201,10 +201,12 @@ instruct encodePAndStoreN_shenandoah(memory dst, iRegPsrc src, iRegPdstNoScratch
// ---------------------- LOAD-STORES -----------------------------------
//
-instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+// Strong CAS also handles WeakCompareAndSwap* on PPC, see JDK-8385633.
+instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndSwapN mem_ptr (Binary src1 src2)));
+ match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2)));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump
format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as bool" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
@@ -214,7 +216,6 @@ instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_pt
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ false,
/* is_narrow */ true,
/* weak */ false,
@@ -223,10 +224,11 @@ instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_pt
ins_pipe(pipe_class_default);
%}
-instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndSwapN mem_ptr (Binary src1 src2)));
+ match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2)));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump
format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as bool" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
@@ -236,7 +238,6 @@ instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst me
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ false,
/* is_narrow */ true,
/* weak */ false,
@@ -245,9 +246,10 @@ instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst me
ins_pipe(pipe_class_default);
%}
-instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndSwapP mem_ptr (Binary src1 src2)));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
+ match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2)));
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n));
format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %}
ins_encode %{
@@ -258,7 +260,6 @@ instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_pt
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ false,
/* is_narrow */ false,
/* weak */ false,
@@ -267,9 +268,10 @@ instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_pt
ins_pipe(pipe_class_default);
%}
-instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndSwapP mem_ptr (Binary src1 src2)));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
+ match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2)));
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n));
format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %}
ins_encode %{
@@ -280,7 +282,6 @@ instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst me
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ false,
/* is_narrow */ false,
/* weak */ false,
@@ -289,98 +290,10 @@ instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst me
ins_pipe(pipe_class_default);
%}
-instruct weakCompareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
- match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2)));
- predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
- format %{ "weak CMPXCHGW acq $res, $mem_ptr, $src1, $src2; as bool" %}
- ins_encode %{
- ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
- $res$$Register,
- $mem_ptr$$Register,
- $src1$$Register,
- $src2$$Register,
- $tmp1$$Register,
- $tmp2$$Register,
- $tmp3$$Register,
- /* exchange */ false,
- /* is_narrow */ true,
- /* weak */ true,
- /* acquire */ false);
- %}
- ins_pipe(pipe_class_default);
-%}
-
-instruct weakCompareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
- match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2)));
- predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
- format %{ "weak CMPXCHGW acq $res, $mem_ptr, $src1, $src2; as bool" %}
- ins_encode %{
- ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
- $res$$Register,
- $mem_ptr$$Register,
- $src1$$Register,
- $src2$$Register,
- $tmp1$$Register,
- $tmp2$$Register,
- $tmp3$$Register,
- /* exchange */ false,
- /* is_narrow */ true,
- /* weak */ true,
- /* acquire */ true);
- %}
- ins_pipe(pipe_class_default);
-%}
-
-instruct weakCompareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
- match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2)));
- predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
- format %{ "weak CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %}
- ins_encode %{
- ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
- $res$$Register,
- $mem_ptr$$Register,
- $src1$$Register,
- $src2$$Register,
- $tmp1$$Register,
- $tmp2$$Register,
- $tmp3$$Register,
- /* exchange */ false,
- /* is_narrow */ false,
- /* weak */ true,
- /* acquire */ false);
- %}
- ins_pipe(pipe_class_default);
-%}
-
-instruct weakCompareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
- match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2)));
- predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump
- format %{ "weak CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %}
- ins_encode %{
- ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
- $res$$Register,
- $mem_ptr$$Register,
- $src1$$Register,
- $src2$$Register,
- $tmp1$$Register,
- $tmp2$$Register,
- $tmp3$$Register,
- /* exchange */ false,
- /* is_narrow */ false,
- /* weak */ true,
- /* acquire */ true);
- %}
- ins_pipe(pipe_class_default);
-%}
-
-instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndExchangeN mem_ptr (Binary src1 src2)));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0);
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0);
format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as narrow oop" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
@@ -390,7 +303,6 @@ instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst me
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ true,
/* is_narrow */ true,
/* weak */ false,
@@ -399,10 +311,10 @@ instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst me
ins_pipe(pipe_class_default);
%}
-instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndExchangeN mem_ptr (Binary src1 src2)));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0);
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0);
format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as narrow oop" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
@@ -412,7 +324,6 @@ instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPds
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ true,
/* is_narrow */ true,
/* weak */ false,
@@ -421,10 +332,10 @@ instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPds
ins_pipe(pipe_class_default);
%}
-instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndExchangeP mem_ptr (Binary src1 src2)));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0);
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0);
format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as ptr; ptr" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
@@ -434,7 +345,6 @@ instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst me
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ true,
/* is_narrow */ false,
/* weak */ false,
@@ -443,10 +353,10 @@ instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst me
ins_pipe(pipe_class_default);
%}
-instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (CompareAndExchangeP mem_ptr (Binary src1 src2)));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0);
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0);
format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as ptr; ptr" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm,
@@ -456,7 +366,6 @@ instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPds
$src2$$Register,
$tmp1$$Register,
$tmp2$$Register,
- $tmp3$$Register,
/* exchange */ true,
/* is_narrow */ false,
/* weak */ false,
@@ -465,10 +374,10 @@ instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPds
ins_pipe(pipe_class_default);
%}
-instruct getAndSetP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct getAndSetP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (GetAndSetP mem_ptr src));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0);
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0);
format %{ "GetAndSetP $res, $mem_ptr, $src" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->get_and_set_c2(this, masm,
@@ -476,16 +385,15 @@ instruct getAndSetP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src, iRe
$src$$Register,
$mem_ptr$$Register,
$tmp1$$Register,
- $tmp2$$Register,
- $tmp3$$Register);
+ $tmp2$$Register);
%}
ins_pipe(pipe_class_default);
%}
-instruct getAndSetN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{
+instruct getAndSetN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{
match(Set res (GetAndSetN mem_ptr src));
predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0));
- effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0);
+ effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0);
format %{ "GetAndSetN $res, $mem_ptr, $src" %}
ins_encode %{
ShenandoahBarrierSet::assembler()->get_and_set_c2(this, masm,
@@ -493,8 +401,7 @@ instruct getAndSetN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src, iRe
$src$$Register,
$mem_ptr$$Register,
$tmp1$$Register,
- $tmp2$$Register,
- $tmp3$$Register);
+ $tmp2$$Register);
%}
ins_pipe(pipe_class_default);
%}
diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc.hpp b/src/hotspot/cpu/ppc/interp_masm_ppc.hpp
index 275ff92c699..45af9bfc252 100644
--- a/src/hotspot/cpu/ppc/interp_masm_ppc.hpp
+++ b/src/hotspot/cpu/ppc/interp_masm_ppc.hpp
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012, 2025 SAP SE. All rights reserved.
+ * Copyright (c) 2012, 2026 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -264,8 +264,6 @@ class InterpreterMacroAssembler: public MacroAssembler {
void profile_switch_default(Register scratch1, Register scratch2);
void profile_switch_case(Register index, Register scratch1,Register scratch2, Register scratch3);
void profile_null_seen(Register Rscratch1, Register Rscratch2);
- void record_klass_in_profile(Register receiver, Register scratch1, Register scratch2);
- void record_klass_in_profile_helper(Register receiver, Register scratch1, Register scratch2, int start_row, Label& done);
// Argument and return type profiling.
void profile_obj_type(Register obj, Register mdo_addr_base, RegisterOrConstant mdo_addr_offs, Register tmp, Register tmp2);
diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
index a1798289b62..789f8da9574 100644
--- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
+++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
@@ -1348,7 +1348,7 @@ void InterpreterMacroAssembler::profile_virtual_call(Register Rreceiver,
test_method_data_pointer(profile_continue);
// Record the receiver type.
- record_klass_in_profile(Rreceiver, Rscratch1, Rscratch2);
+ profile_receiver_type(Rreceiver, R28_mdx, 0, Rscratch1, Rscratch2);
// The method data pointer needs to be updated to reflect the new target.
update_mdp_by_constant(in_bytes(VirtualCallData::virtual_call_data_size()));
@@ -1367,7 +1367,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register Rklass, Register Rscr
mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size());
// Record the object type.
- record_klass_in_profile(Rklass, Rscratch1, Rscratch2);
+ profile_receiver_type(Rklass, R28_mdx, 0, Rscratch1, Rscratch2);
}
// The method data pointer needs to be updated.
@@ -1481,88 +1481,6 @@ void InterpreterMacroAssembler::profile_null_seen(Register Rscratch1, Register R
}
}
-void InterpreterMacroAssembler::record_klass_in_profile(Register Rreceiver,
- Register Rscratch1, Register Rscratch2) {
- assert(ProfileInterpreter, "must be profiling");
- assert_different_registers(Rreceiver, Rscratch1, Rscratch2);
-
- Label done;
- record_klass_in_profile_helper(Rreceiver, Rscratch1, Rscratch2, 0, done);
- bind (done);
-}
-
-void InterpreterMacroAssembler::record_klass_in_profile_helper(
- Register receiver, Register scratch1, Register scratch2,
- int start_row, Label& done) {
- if (TypeProfileWidth == 0) {
- increment_mdp_data_at(in_bytes(CounterData::count_offset()), scratch1, scratch2);
- return;
- }
-
- int last_row = VirtualCallData::row_limit() - 1;
- assert(start_row <= last_row, "must be work left to do");
- // Test this row for both the receiver and for null.
- // Take any of three different outcomes:
- // 1. found receiver => increment count and goto done
- // 2. found null => keep looking for case 1, maybe allocate this cell
- // 3. found something else => keep looking for cases 1 and 2
- // Case 3 is handled by a recursive call.
- for (int row = start_row; row <= last_row; row++) {
- Label next_test;
- bool test_for_null_also = (row == start_row);
-
- // See if the receiver is receiver[n].
- int recvr_offset = in_bytes(VirtualCallData::receiver_offset(row));
- test_mdp_data_at(recvr_offset, receiver, next_test, scratch1);
- // delayed()->tst(scratch);
-
- // The receiver is receiver[n]. Increment count[n].
- int count_offset = in_bytes(VirtualCallData::receiver_count_offset(row));
- increment_mdp_data_at(count_offset, scratch1, scratch2);
- b(done);
- bind(next_test);
-
- if (test_for_null_also) {
- Label found_null;
- // Failed the equality check on receiver[n]... Test for null.
- if (start_row == last_row) {
- // The only thing left to do is handle the null case.
- // Scratch1 contains test_out from test_mdp_data_at.
- cmpdi(CR0, scratch1, 0);
- beq(CR0, found_null);
- // Receiver did not match any saved receiver and there is no empty row for it.
- // Increment total counter to indicate polymorphic case.
- increment_mdp_data_at(in_bytes(CounterData::count_offset()), scratch1, scratch2);
- b(done);
- bind(found_null);
- break;
- }
- // Since null is rare, make it be the branch-taken case.
- cmpdi(CR0, scratch1, 0);
- beq(CR0, found_null);
-
- // Put all the "Case 3" tests here.
- record_klass_in_profile_helper(receiver, scratch1, scratch2, start_row + 1, done);
-
- // Found a null. Keep searching for a matching receiver,
- // but remember that this is an empty (unused) slot.
- bind(found_null);
- }
- }
-
- // In the fall-through case, we found no matching receiver, but we
- // observed the receiver[start_row] is null.
-
- // Fill in the receiver field and increment the count.
- int recvr_offset = in_bytes(VirtualCallData::receiver_offset(start_row));
- set_mdp_data_at(recvr_offset, receiver);
- int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row));
- li(scratch1, DataLayout::counter_increment);
- set_mdp_data_at(count_offset, scratch1);
- if (start_row > 0) {
- b(done);
- }
-}
// Argument and return type profilig.
// kills: tmp, tmp2, R0, CR0, CR1
diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
index 95d58d470c8..b5bfcb0fced 100644
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
@@ -4329,6 +4329,173 @@ void MacroAssembler::multiply_to_len(Register x, Register xlen,
bind(L_done);
} // multiply_to_len
+void MacroAssembler::increment_mem64(Register base, RegisterOrConstant ind_or_offs, int val, Register tmp) {
+ ld(tmp, ind_or_offs, base);
+ addi(tmp, tmp, val);
+ std(tmp, ind_or_offs, base);
+}
+
+// Handle the receiver type profile update given the "recv" klass.
+//
+// Normally updates the ReceiverData (RD) that starts at "mdp" + "mdp_offset".
+// If there are no matching or claimable receiver entries in RD, updates
+// the polymorphic counter.
+//
+// This code expected to run by either the interpreter or JIT-ed code, without
+// extra synchronization. For safety, receiver cells are claimed atomically, which
+// avoids grossly misrepresenting the profiles under concurrent updates. For speed,
+// counter updates are not atomic.
+//
+void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_offset, Register tmp1, Register tmp2) {
+ assert_different_registers(recv, mdp, tmp1, tmp2);
+
+ int base_receiver_offset = in_bytes(ReceiverTypeData::receiver_offset(0));
+ int poly_count_offset = in_bytes(CounterData::count_offset());
+ int receiver_step = in_bytes(ReceiverTypeData::receiver_offset(1)) - base_receiver_offset;
+ int receiver_to_count_step = in_bytes(ReceiverTypeData::receiver_count_offset(0)) - base_receiver_offset;
+
+ // Adjust for MDP offsets.
+ base_receiver_offset += mdp_offset;
+ poly_count_offset += mdp_offset;
+
+#ifdef ASSERT
+ // We are about to walk the MDO slots without asking for offsets.
+ // Check that our math hits all the right spots.
+ for (uint c = 0; c < ReceiverTypeData::row_limit(); c++) {
+ int real_recv_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_offset(c));
+ int real_count_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_count_offset(c));
+ int offset = base_receiver_offset + receiver_step*c;
+ int count_offset = offset + receiver_to_count_step;
+ assert(offset == real_recv_offset, "receiver slot math");
+ assert(count_offset == real_count_offset, "receiver count math");
+ }
+ int real_poly_count_offset = mdp_offset + in_bytes(CounterData::count_offset());
+ assert(poly_count_offset == real_poly_count_offset, "poly counter math");
+#endif
+
+ // Corner case: no profile table. Increment poly counter and exit.
+ if (ReceiverTypeData::row_limit() == 0) {
+ increment_mem64(mdp, poly_count_offset, DataLayout::counter_increment, tmp1);
+ return;
+ }
+
+ Label L_loop_search_receiver, L_loop_search_empty;
+ Label L_restart, L_found_recv, L_found_empty, L_count_update;
+ Register offset = tmp1, count = tmp2;
+
+ // The code here recognizes three major cases:
+ // A. Fastest: receiver found in the table
+ // B. Fast: no receiver in the table, and the table is full
+ // C. Slow: no receiver in the table, free slots in the table
+ //
+ // The case A performance is most important, as perfectly-behaved code would end up
+ // there, especially with larger TypeProfileWidth. The case B performance is
+ // important as well, this is where bulk of code would land for normally megamorphic
+ // cases. The case C performance is not essential, its job is to deal with installation
+ // races, we optimize for code density instead. Case C needs to make sure that receiver
+ // rows are only claimed once. This makes sure we never overwrite a row for another
+ // receiver and never duplicate the receivers in the list, making profile type-accurate.
+ //
+ // It is very tempting to handle these cases in a single loop, and claim the first slot
+ // without checking the rest of the table. But, profiling code should tolerate free slots
+ // in the table, as class unloading can clear them. After such cleanup, the receiver
+ // we need might be _after_ the free slot. Therefore, we need to let at least full scan
+ // to complete, before trying to install new slots. Splitting the code in several tight
+ // loops also helpfully optimizes for cases A and B.
+ //
+ // This code is effectively:
+ //
+ // restart:
+ // // Fastest: receiver is already installed
+ // for (i = 0; i < receiver_count(); i++) {
+ // if (receiver(i) == recv) goto found_recv(i);
+ // }
+ //
+ // // Fast: no receiver, but profile is not full
+ // for (i = 0; i < receiver_count(); i++) {
+ // if (receiver(i) == null) goto found_null(i);
+ // }
+ //
+ // // Slow: profile is full, polymorphic case
+ // count++;
+ // return
+ //
+ // // Slow: try to install receiver
+ // found_null(i):
+ // CAS(&receiver(i), null, recv);
+ // goto restart
+ //
+ // found_recv(i):
+ // *receiver_count(i)++
+ //
+
+ if (count != noreg) {
+ li(count, ReceiverTypeData::row_limit());
+ }
+
+ bind(L_restart);
+
+ // Fastest: receiver is already installed
+ if (count != noreg) {
+ mtctr(count);
+ } else {
+ li(R0, ReceiverTypeData::row_limit());
+ mtctr(R0);
+ }
+ li(offset, base_receiver_offset);
+ bind(L_loop_search_receiver);
+ ldx(R0, offset, mdp);
+ cmpd(CR0, R0, recv);
+ beq(CR0, L_found_recv);
+ addi(offset, offset, receiver_step);
+ bdnz(L_loop_search_receiver);
+
+ // Fast: no receiver, but profile is full
+ if (count != noreg) {
+ mtctr(count);
+ } else {
+ li(R0, ReceiverTypeData::row_limit());
+ mtctr(R0);
+ }
+ li(offset, base_receiver_offset);
+ bind(L_loop_search_empty);
+ ldx(R0, offset, mdp);
+ cmpdi(CR0, R0, 0);
+ beq(CR0, L_found_empty);
+ addi(offset, offset, receiver_step);
+ bdnz(L_loop_search_empty);
+
+ // Slow: Receiver is not found and table is full.
+ // Increment polymorphic counter instead of receiver slot.
+ li(offset, poly_count_offset);
+ b(L_count_update);
+
+ // Slowest: try to install receiver
+ bind(L_found_empty);
+
+ // Atomically swing receiver slot: null -> recv.
+ {
+ Register receiver_addr = offset;
+ add(receiver_addr, mdp, offset); // kills offset
+ cmpxchgd(CR0, R0, RegisterOrConstant(0), recv, receiver_addr, MemBarNone, cmpxchgx_hint_atomic_update(),
+ noreg, nullptr, /* check without ldarx first */ false, /* weak */ true);
+ }
+
+ // CAS success means the slot now has the receiver we want. CAS failure means
+ // something had claimed the slot concurrently: it can be the same receiver we want,
+ // or something else. Since this is a slow path, we can optimize for code density,
+ // and just restart the search from the beginning.
+ b(L_restart);
+
+ // Found a receiver, convert its slot offset to corresponding count offset.
+ bind(L_found_recv);
+ addi(offset, offset, receiver_to_count_step);
+
+ // Finally, update the counter
+ bind(L_count_update);
+ increment_mem64(mdp, offset, DataLayout::counter_increment, /* temp */ (count != noreg) ? count : recv);
+}
+
#ifdef ASSERT
void MacroAssembler::asm_assert(AsmAssertCond cond, const char *msg) {
Label ok;
diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
index 21ab192373f..bbfa75f5151 100644
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
@@ -870,6 +870,12 @@ class MacroAssembler: public Assembler {
Register tmp6, Register tmp7, Register tmp8, Register tmp9, Register tmp10,
Register tmp11, Register tmp12, Register tmp13);
+ // non-atomic 64-bit memory increment by simm16
+ void increment_mem64(Register base, RegisterOrConstant ind_or_offs, int val, Register tmp);
+
+ // Bytecode profiling (tmp2 = noreg is allowed, but then recv is killed)
+ void profile_receiver_type(Register recv, Register mdp, int mdp_offset, Register tmp1, Register tmp2);
+
// Emitters for CRC32 calculation.
// A note on invertCRC:
// Unfortunately, internal representation of crc differs between CRC32 and CRC32C.
diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad
index 7360ed604f1..ea1768c6afd 100644
--- a/src/hotspot/cpu/ppc/ppc.ad
+++ b/src/hotspot/cpu/ppc/ppc.ad
@@ -3475,30 +3475,34 @@ frame %{
// 4 what apparently works and saves us some spills.
return_addr(STACK 4);
- // Location of native (C/C++) and interpreter return values. This
- // is specified to be the same as Java. In the 32-bit VM, long
- // values are actually returned from native calls in O0:O1 and
- // returned to the interpreter in I0:I1. The copying to and from
- // the register pairs is done by the appropriate call and epilog
- // opcodes. This simplifies the register allocator.
- c_return_value %{
- assert((ideal_reg >= Op_RegI && ideal_reg <= Op_RegL) ||
- (ideal_reg == Op_RegN && CompressedOops::base() == nullptr && CompressedOops::shift() == 0),
- "only return normal values");
- // enum names from opcodes.hpp: Op_Node Op_Set Op_RegN Op_RegI Op_RegP Op_RegF Op_RegD Op_RegL
- static int typeToRegLo[Op_RegL+1] = { 0, 0, R3_num, R3_num, R3_num, F1_num, F1_num, R3_num };
- static int typeToRegHi[Op_RegL+1] = { 0, 0, OptoReg::Bad, R3_H_num, R3_H_num, OptoReg::Bad, F1_H_num, R3_H_num };
- return OptoRegPair(typeToRegHi[ideal_reg], typeToRegLo[ideal_reg]);
- %}
-
// Location of compiled Java return values. Same as C
return_value %{
assert((ideal_reg >= Op_RegI && ideal_reg <= Op_RegL) ||
(ideal_reg == Op_RegN && CompressedOops::base() == nullptr && CompressedOops::shift() == 0),
"only return normal values");
- // enum names from opcodes.hpp: Op_Node Op_Set Op_RegN Op_RegI Op_RegP Op_RegF Op_RegD Op_RegL
- static int typeToRegLo[Op_RegL+1] = { 0, 0, R3_num, R3_num, R3_num, F1_num, F1_num, R3_num };
- static int typeToRegHi[Op_RegL+1] = { 0, 0, OptoReg::Bad, R3_H_num, R3_H_num, OptoReg::Bad, F1_H_num, R3_H_num };
+ // enum names from opcodes.hpp
+ static int typeToRegLo[Op_RegL+1] = {
+ 0, // Op_Node
+ 0, // Op_Set
+ R3_num, // Op_RegN
+ R3_num, // Op_RegI
+ R3_num, // Op_RegP
+ F1_num, // Op_RegF
+ F1_num, // Op_RegD
+ R3_num, // Op_RegL
+ };
+
+ static int typeToRegHi[Op_RegL+1] = {
+ 0, // Op_Node
+ 0, // Op_Set
+ OptoReg::Bad, // Op_RegN
+ OptoReg::Bad, // Op_RegI
+ R3_H_num, // Op_RegP
+ OptoReg::Bad, // Op_RegF
+ F1_H_num, // Op_RegD
+ R3_H_num // Op_RegL
+ };
+
return OptoRegPair(typeToRegHi[ideal_reg], typeToRegLo[ideal_reg]);
%}
%}
diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
index ee8ff1b308f..eec5f9a5165 100644
--- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
@@ -28,7 +28,6 @@
#include "gc/shenandoah/mode/shenandoahMode.hpp"
#include "gc/shenandoah/shenandoahBarrierSet.hpp"
#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp"
-#include "gc/shenandoah/shenandoahForwarding.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
#include "gc/shenandoah/shenandoahRuntime.hpp"
@@ -174,53 +173,6 @@ void ShenandoahBarrierSetAssembler::satb_barrier(MacroAssembler* masm,
__ bind(done);
}
-void ShenandoahBarrierSetAssembler::resolve_forward_pointer(MacroAssembler* masm, Register dst, Register tmp) {
- assert(ShenandoahLoadRefBarrier || ShenandoahCASBarrier, "Should be enabled");
-
- Label is_null;
- __ beqz(dst, is_null);
- resolve_forward_pointer_not_null(masm, dst, tmp);
- __ bind(is_null);
-}
-
-// IMPORTANT: This must preserve all registers, even t0 and t1, except those explicitly
-// passed in.
-void ShenandoahBarrierSetAssembler::resolve_forward_pointer_not_null(MacroAssembler* masm, Register dst, Register tmp) {
- assert(ShenandoahLoadRefBarrier || ShenandoahCASBarrier, "Should be enabled");
- // The below loads the mark word, checks if the lowest two bits are
- // set, and if so, clear the lowest two bits and copy the result
- // to dst. Otherwise it leaves dst alone.
- // Implementing this is surprisingly awkward. I do it here by:
- // - Inverting the mark word
- // - Test lowest two bits == 0
- // - If so, set the lowest two bits
- // - Invert the result back, and copy to dst
- RegSet saved_regs = RegSet::of(t2);
- bool borrow_reg = (tmp == noreg);
- if (borrow_reg) {
- // No free registers available. Make one useful.
- tmp = t0;
- if (tmp == dst) {
- tmp = t1;
- }
- saved_regs += RegSet::of(tmp);
- }
-
- assert_different_registers(tmp, dst, t2);
- __ push_reg(saved_regs, sp);
-
- Label done;
- __ ld(tmp, Address(dst, oopDesc::mark_offset_in_bytes()));
- __ xori(tmp, tmp, -1); // eon with 0 is equivalent to XOR with -1
- __ andi(t2, tmp, markWord::lock_mask_in_place);
- __ bnez(t2, done);
- __ ori(tmp, tmp, markWord::marked_value);
- __ xori(dst, tmp, -1); // eon with 0 is equivalent to XOR with -1
- __ bind(done);
-
- __ pop_reg(saved_regs, sp);
-}
-
void ShenandoahBarrierSetAssembler::load_reference_barrier(MacroAssembler* masm,
Register dst,
Address load_addr,
@@ -481,90 +433,6 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl
__ bind(done);
}
-// Special Shenandoah CAS implementation that handles false negatives due
-// to concurrent evacuation. The service is more complex than a
-// traditional CAS operation because the CAS operation is intended to
-// succeed if the reference at addr exactly matches expected or if the
-// reference at addr holds a pointer to a from-space object that has
-// been relocated to the location named by expected. There are two
-// races that must be addressed:
-// a) A parallel thread may mutate the contents of addr so that it points
-// to a different object. In this case, the CAS operation should fail.
-// b) A parallel thread may heal the contents of addr, replacing a
-// from-space pointer held in addr with the to-space pointer
-// representing the new location of the object.
-// Upon entry to cmpxchg_oop, it is assured that new_val equals null
-// or it refers to an object that is not being evacuated out of
-// from-space, or it refers to the to-space version of an object that
-// is being evacuated out of from-space.
-//
-// By default the value held in the result register following execution
-// of the generated code sequence is 0 to indicate failure of CAS,
-// non-zero to indicate success. If is_cae, the result is the value most
-// recently fetched from addr rather than a boolean success indicator.
-//
-// Clobbers t0, t1
-void ShenandoahBarrierSetAssembler::cmpxchg_oop(MacroAssembler* masm,
- Register addr,
- Register expected,
- Register new_val,
- Assembler::Aqrl acquire,
- Assembler::Aqrl release,
- bool is_cae,
- Register result) {
- bool is_narrow = UseCompressedOops;
- Assembler::operand_size size = is_narrow ? Assembler::uint32 : Assembler::int64;
-
- assert_different_registers(addr, expected, t0, t1);
- assert_different_registers(addr, new_val, t0, t1);
-
- Label retry, success, fail, done;
-
- __ bind(retry);
-
- // Step1: Try to CAS.
- __ cmpxchg(addr, expected, new_val, size, acquire, release, /* result */ t1);
-
- // If success, then we are done.
- __ beq(expected, t1, success);
-
- // Step2: CAS failed, check the forwarded pointer.
- __ mv(t0, t1);
-
- if (is_narrow) {
- __ decode_heap_oop(t0, t0);
- }
- resolve_forward_pointer(masm, t0);
-
- __ encode_heap_oop(t0, t0);
-
- // Report failure when the forwarded oop was not expected.
- __ bne(t0, expected, fail);
-
- // Step 3: CAS again using the forwarded oop.
- __ cmpxchg(addr, t1, new_val, size, acquire, release, /* result */ t0);
-
- // Retry when failed.
- __ bne(t0, t1, retry);
-
- __ bind(success);
- if (is_cae) {
- __ mv(result, expected);
- } else {
- __ mv(result, 1);
- }
- __ j(done);
-
- __ bind(fail);
- if (is_cae) {
- __ mv(result, t0);
- } else {
- __ mv(result, zr);
- }
-
- __ bind(done);
-}
-
void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
Register start, Register count, Register tmp) {
assert(ShenandoahCardBarrier, "Did you mean to enable ShenandoahCardBarrier?");
@@ -792,7 +660,7 @@ void ShenandoahBarrierSetAssembler::load_c2(const MachNode* node, MacroAssembler
void ShenandoahBarrierSetAssembler::store_c2(const MachNode* node, MacroAssembler* masm, Address dst, bool dst_narrow,
Register src, bool src_narrow, Register tmp1, Register tmp2, Register tmp3) {
- ShenandoahBarrierStubC2::store_pre(masm, node, tmp1, dst, tmp2, tmp3, dst_narrow);
+ ShenandoahBarrierStubC2::store_pre(masm, node, dst, tmp1, tmp2, tmp3, dst_narrow);
// Do the actual store
if (dst_narrow) {
@@ -820,7 +688,7 @@ void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, Mac
const Assembler::Aqrl release = Assembler::rl;
const Assembler::operand_size size = narrow ? Assembler::uint32 : Assembler::int64;
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, Address(addr), tmp2, tmp3, narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, Address(addr), tmp1, tmp2, tmp3, narrow);
// CAS!
__ cmpxchg(addr, oldval, newval, size, acquire, release, /* result */ res, !exchange /* result_as_bool */);
@@ -832,7 +700,7 @@ void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAs
Register newval, Register addr, Register tmp1, Register tmp2, Register tmp3, bool is_acquire) {
const bool is_narrow = node->bottom_type()->isa_narrowoop();
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, Address(addr, 0), tmp2, tmp3, is_narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, Address(addr, 0), tmp1, tmp2, tmp3, is_narrow);
if (is_narrow) {
if (is_acquire) {
@@ -1044,12 +912,9 @@ void ShenandoahBarrierStubC2::lrb(MacroAssembler& masm) {
if (c_rarg0 == _obj) {
__ la(c_rarg1, _addr);
} else if (c_rarg1 == _obj) {
- // Set up arguments in reverse, and then flip them
- __ la(c_rarg0, _addr);
- // flip them
- __ mv(_tmp1, c_rarg0);
- __ mv(c_rarg0, c_rarg1);
- __ mv(c_rarg1, _tmp1);
+ __ mv(_tmp1, c_rarg1);
+ __ la(c_rarg1, _addr);
+ __ mv(c_rarg0, _tmp1);
} else {
assert_different_registers(c_rarg1, _obj);
__ la(c_rarg1, _addr);
diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp
index d1260eac57b..d41809f1ef7 100644
--- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp
@@ -54,8 +54,6 @@ private:
void card_barrier(MacroAssembler* masm, Register obj);
- void resolve_forward_pointer(MacroAssembler* masm, Register dst, Register tmp = noreg);
- void resolve_forward_pointer_not_null(MacroAssembler* masm, Register dst, Register tmp = noreg);
void load_reference_barrier(MacroAssembler* masm, Register dst, Address load_addr, DecoratorSet decorators);
void gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
@@ -81,8 +79,6 @@ public:
Register obj, Register tmp, Label& slowpath);
virtual void try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj,
Register tmp, Label& slow_path);
- void cmpxchg_oop(MacroAssembler* masm, Register addr, Register expected, Register new_val,
- Assembler::Aqrl acquire, Assembler::Aqrl release, bool is_cae, Register result);
#ifdef COMPILER1
void gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub);
diff --git a/src/hotspot/cpu/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp
index 18ca120bdfa..f05b9ff7791 100644
--- a/src/hotspot/cpu/riscv/globals_riscv.hpp
+++ b/src/hotspot/cpu/riscv/globals_riscv.hpp
@@ -120,10 +120,10 @@ define_pd_global(intx, InlineSmallCode, 1000);
product(bool, UseZvbb, false, EXPERIMENTAL, "Use Zvbb instructions") \
product(bool, UseZvbc, false, EXPERIMENTAL, "Use Zvbc instructions") \
product(bool, UseZvfh, false, DIAGNOSTIC, "Use Zvfh instructions") \
- product(bool, UseZvkn, false, EXPERIMENTAL, \
+ product(bool, UseZvkg, false, DIAGNOSTIC, "Use Zvkg instructions") \
+ product(bool, UseZvkn, false, DIAGNOSTIC, \
"Use Zvkn group extension, Zvkned, Zvknhb, Zvkb, Zvkt") \
product(bool, UseCtxFencei, false, EXPERIMENTAL, \
- "Use PR_RISCV_CTX_SW_FENCEI_ON to avoid explicit icache flush") \
- product(bool, UseZvkg, false, EXPERIMENTAL, "Use Zvkg instructions")
+ "Use PR_RISCV_CTX_SW_FENCEI_ON to avoid explicit icache flush")
#endif // CPU_RISCV_GLOBALS_RISCV_HPP
diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
index 443f3e3d17f..bb56acb3f38 100644
--- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
+++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
@@ -1221,6 +1221,7 @@ void InterpreterMacroAssembler::notify_method_exit(
// track stack depth. If it is possible to enter interp_only_mode we add
// the code to check if the event should be sent.
if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) {
+ Label L;
// Note: frame::interpreter_frame_result has a dependency on how the
// method result is saved across the call to post_method_exit. If this
// is changed then the interpreter_frame_result implementation will
@@ -1228,8 +1229,18 @@ void InterpreterMacroAssembler::notify_method_exit(
// template interpreter will leave the result on the top of the stack.
push(state);
+
+ ld(t1, Address(xthread, JavaThread::jvmti_thread_state_offset()));
+ beqz(t1, L); // if (thread->jvmti_thread_state() == nullptr) exit;
+
+ lwu(t1, Address(t1, JvmtiThreadState::frame_pop_cnt_offset()));
+ lwu(t0, Address(xthread, JavaThread::interp_only_mode_offset()));
+ orr(t0, t0, t1);
+ beqz(t0, L);
+
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit));
+ bind(L);
pop(state);
}
diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
index 02798d5204a..d93329544a7 100644
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
@@ -593,7 +593,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
Register offset = t1;
Label L_loop_search_receiver, L_loop_search_empty;
- Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update;
+ Label L_restart, L_found_recv, L_found_empty, L_count_update;
// The code here recognizes three major cases:
// A. Fastest: receiver found in the table
@@ -623,21 +623,20 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
// if (receiver(i) == recv) goto found_recv(i);
// }
//
- // // Fast: no receiver, but profile is full
+ // // Fast: no receiver, but profile is not full
// for (i = 0; i < receiver_count(); i++) {
// if (receiver(i) == null) goto found_null(i);
// }
- // goto polymorphic
+ //
+ // // Slow: profile is full, polymorphic case
+ // count++;
+ // return
//
// // Slow: try to install receiver
// found_null(i):
// CAS(&receiver(i), null, recv);
// goto restart
//
- // polymorphic:
- // count++;
- // return
- //
// found_recv(i):
// *receiver_count(i)++
//
@@ -654,7 +653,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
sub(t0, offset, end_receiver_offset);
bnez(t0, L_loop_search_receiver);
- // Fast: no receiver, but profile is full
+ // Fast: no receiver, but profile is not full
mv(offset, base_receiver_offset);
bind(L_loop_search_empty);
add(t0, mdp, offset);
@@ -663,9 +662,13 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
add(offset, offset, receiver_step);
sub(t0, offset, end_receiver_offset);
bnez(t0, L_loop_search_empty);
- j(L_polymorphic);
- // Slow: try to install receiver
+ // Slow: Receiver is not found and table is full.
+ // Increment polymorphic counter instead of receiver slot.
+ mv(offset, poly_count_offset);
+ j(L_count_update);
+
+ // Slowest: try to install receiver
bind(L_found_empty);
// Atomically swing receiver slot: null -> recv.
@@ -683,16 +686,11 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
// and just restart the search from the beginning.
j(L_restart);
- // Counter updates:
- // Increment polymorphic counter instead of receiver slot.
- bind(L_polymorphic);
- mv(offset, poly_count_offset);
- j(L_count_update);
-
// Found a receiver, convert its slot offset to corresponding count offset.
bind(L_found_recv);
add(offset, offset, receiver_to_count_step);
+ // Finally, update the counter
bind(L_count_update);
add(t1, mdp, offset);
increment(Address(t1), DataLayout::counter_increment);
@@ -4178,50 +4176,6 @@ void MacroAssembler::safepoint_poll(Label& slow_path, bool at_return, bool in_nm
}
}
-void MacroAssembler::cmpxchgptr(Register oldv, Register newv, Register addr, Register tmp,
- Label &succeed, Label *fail) {
- assert_different_registers(addr, tmp, t0);
- assert_different_registers(newv, tmp, t0);
- assert_different_registers(oldv, tmp, t0);
-
- // oldv holds comparison value
- // newv holds value to write in exchange
- // addr identifies memory word to compare against/update
- if (UseZacas) {
- mv(tmp, oldv);
- atomic_cas(tmp, newv, addr, Assembler::int64, Assembler::aq, Assembler::rl);
- beq(tmp, oldv, succeed);
- } else {
- Label retry_load, nope;
- bind(retry_load);
- // Load reserved from the memory location
- load_reserved(tmp, addr, int64, Assembler::aqrl);
- // Fail and exit if it is not what we expect
- bne(tmp, oldv, nope);
- // If the store conditional succeeds, tmp will be zero
- store_conditional(tmp, newv, addr, int64, Assembler::rl);
- beqz(tmp, succeed);
- // Retry only when the store conditional failed
- j(retry_load);
-
- bind(nope);
- }
-
- // neither amocas nor lr/sc have an implied barrier in the failing case
- membar(AnyAny);
-
- mv(oldv, tmp);
- if (fail != nullptr) {
- j(*fail);
- }
-}
-
-void MacroAssembler::cmpxchg_obj_header(Register oldv, Register newv, Register obj, Register tmp,
- Label &succeed, Label *fail) {
- assert(oopDesc::mark_offset_in_bytes() == 0, "assumption");
- cmpxchgptr(oldv, newv, obj, tmp, succeed, fail);
-}
-
void MacroAssembler::load_reserved(Register dst,
Register addr,
Assembler::operand_size size,
diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
index 6e592b5c852..a5ad7eeaa5f 100644
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
@@ -1203,8 +1203,6 @@ public:
#undef INSN_ENTRY_RELOC
- void cmpxchg_obj_header(Register oldv, Register newv, Register obj, Register tmp, Label &succeed, Label *fail);
- void cmpxchgptr(Register oldv, Register newv, Register addr, Register tmp, Label &succeed, Label *fail);
void cmpxchg(Register addr, Register expected,
Register new_val,
Assembler::operand_size size,
diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp
index 03c90a499fb..cc8ca7a1f47 100644
--- a/src/hotspot/cpu/s390/interp_masm_s390.cpp
+++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp
@@ -2003,9 +2003,22 @@ void InterpreterMacroAssembler::notify_method_exit(bool native_method,
// depth. If it is possible to enter interp_only_mode we add
// the code to check if the event should be sent.
if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) {
+ NearLabel jvmti_post_done;
+
+ // if (thread->jvmti_thread_state() == nullptr) exit;
+ z_ltg(Z_R1_scratch, Address(Z_thread, JavaThread::jvmti_thread_state_offset()));
+ z_brz(jvmti_post_done);
+
+ // if (interp_only_mode() == false && frame_pop_cnt() == 0) exit;
+ z_lgf(Z_R1_scratch, Address(Z_R1_scratch, JvmtiThreadState::frame_pop_cnt_offset()));
+ z_o(Z_R1_scratch, Address(Z_thread, JavaThread::interp_only_mode_offset()));
+ z_brz(jvmti_post_done);
+
if (!native_method) push(state); // see frame::interpreter_frame_result()
call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit));
if (!native_method) pop(state);
+
+ bind(jvmti_post_done);
}
}
diff --git a/src/hotspot/cpu/x86/frame_x86.cpp b/src/hotspot/cpu/x86/frame_x86.cpp
index 27741ee3bb1..2b06f9ee80c 100644
--- a/src/hotspot/cpu/x86/frame_x86.cpp
+++ b/src/hotspot/cpu/x86/frame_x86.cpp
@@ -298,7 +298,7 @@ void frame::patch_pc(Thread* thread, address pc) {
#ifdef ASSERT
{
- frame f(this->sp(), this->unextended_sp(), this->fp(), pc);
+ frame f(sp(), unextended_sp(), fp(), pc, cb(), oop_map(), is_heap_frame());
assert(f.is_deoptimized_frame() == this->is_deoptimized_frame() && f.pc() == this->pc() && f.raw_pc() == this->raw_pc(),
"must be (f.is_deoptimized_frame(): %d this->is_deoptimized_frame(): %d "
"f.pc(): " INTPTR_FORMAT " this->pc(): " INTPTR_FORMAT " f.raw_pc(): " INTPTR_FORMAT " this->raw_pc(): " INTPTR_FORMAT ")",
diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
index ce8f26fc1b0..fdf10e5b5e6 100644
--- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
@@ -28,7 +28,6 @@
#include "gc/shenandoah/mode/shenandoahMode.hpp"
#include "gc/shenandoah/shenandoahBarrierSet.hpp"
#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp"
-#include "gc/shenandoah/shenandoahForwarding.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
#include "gc/shenandoah/shenandoahRuntime.hpp"
@@ -512,151 +511,6 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl
__ bind(done);
}
-// Special Shenandoah CAS implementation that handles false negatives
-// due to concurrent evacuation.
-void ShenandoahBarrierSetAssembler::cmpxchg_oop(MacroAssembler* masm,
- Register res, Address addr, Register oldval, Register newval,
- bool exchange, Register tmp1, Register tmp2) {
- assert(ShenandoahCASBarrier, "Should only be used when CAS barrier is enabled");
- assert(oldval == rax, "must be in rax for implicit use in cmpxchg");
- assert_different_registers(oldval, tmp1, tmp2);
- assert_different_registers(newval, tmp1, tmp2);
-
- Label L_success, L_failure;
-
- // Remember oldval for retry logic below
- if (UseCompressedOops) {
- __ movl(tmp1, oldval);
- } else {
- __ movptr(tmp1, oldval);
- }
-
- // Step 1. Fast-path.
- //
- // Try to CAS with given arguments. If successful, then we are done.
-
- if (UseCompressedOops) {
- __ lock();
- __ cmpxchgl(newval, addr);
- } else {
- __ lock();
- __ cmpxchgptr(newval, addr);
- }
- __ jcc(Assembler::equal, L_success);
-
- // Step 2. CAS had failed. This may be a false negative.
- //
- // The trouble comes when we compare the to-space pointer with the from-space
- // pointer to the same object. To resolve this, it will suffice to resolve
- // the value from memory -- this will give both to-space pointers.
- // If they mismatch, then it was a legitimate failure.
- //
- // Before reaching to resolve sequence, see if we can avoid the whole shebang
- // with filters.
-
- // Filter: when offending in-memory value is null, the failure is definitely legitimate
- __ testptr(oldval, oldval);
- __ jcc(Assembler::zero, L_failure);
-
- // Filter: when heap is stable, the failure is definitely legitimate
- const Register thread = r15_thread;
- Address gc_state(thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
- __ testb(gc_state, ShenandoahHeap::HAS_FORWARDED);
- __ jcc(Assembler::zero, L_failure);
-
- if (UseCompressedOops) {
- __ movl(tmp2, oldval);
- __ decode_heap_oop(tmp2);
- } else {
- __ movptr(tmp2, oldval);
- }
-
- // Decode offending in-memory value.
- // Test if-forwarded
- __ testb(Address(tmp2, oopDesc::mark_offset_in_bytes()), markWord::marked_value);
- __ jcc(Assembler::noParity, L_failure); // When odd number of bits, then not forwarded
- __ jcc(Assembler::zero, L_failure); // When it is 00, then also not forwarded
-
- // Load and mask forwarding pointer
- __ movptr(tmp2, Address(tmp2, oopDesc::mark_offset_in_bytes()));
- __ shrptr(tmp2, 2);
- __ shlptr(tmp2, 2);
-
- if (UseCompressedOops) {
- __ decode_heap_oop(tmp1); // decode for comparison
- }
-
- // Now we have the forwarded offender in tmp2.
- // Compare and if they don't match, we have legitimate failure
- __ cmpptr(tmp1, tmp2);
- __ jcc(Assembler::notEqual, L_failure);
-
- // Step 3. Need to fix the memory ptr before continuing.
- //
- // At this point, we have from-space oldval in the register, and its to-space
- // address is in tmp2. Let's try to update it into memory. We don't care if it
- // succeeds or not. If it does, then the retrying CAS would see it and succeed.
- // If this fixup fails, this means somebody else beat us to it, and necessarily
- // with to-space ptr store. We still have to do the retry, because the GC might
- // have updated the reference for us.
-
- if (UseCompressedOops) {
- __ encode_heap_oop(tmp2); // previously decoded at step 2.
- }
-
- if (UseCompressedOops) {
- __ lock();
- __ cmpxchgl(tmp2, addr);
- } else {
- __ lock();
- __ cmpxchgptr(tmp2, addr);
- }
-
- // Step 4. Try to CAS again.
- //
- // This is guaranteed not to have false negatives, because oldval is definitely
- // to-space, and memory pointer is to-space as well. Nothing is able to store
- // from-space ptr into memory anymore. Make sure oldval is restored, after being
- // garbled during retries.
- //
- if (UseCompressedOops) {
- __ movl(oldval, tmp2);
- } else {
- __ movptr(oldval, tmp2);
- }
-
- if (UseCompressedOops) {
- __ lock();
- __ cmpxchgl(newval, addr);
- } else {
- __ lock();
- __ cmpxchgptr(newval, addr);
- }
- if (!exchange) {
- __ jccb(Assembler::equal, L_success); // fastpath, peeking into Step 5, no need to jump
- }
-
- // Step 5. If we need a boolean result out of CAS, set the flag appropriately.
- // and promote the result. Note that we handle the flag from both the 1st and 2nd CAS.
- // Otherwise, failure witness for CAE is in oldval on all paths, and we can return.
-
- if (exchange) {
- __ bind(L_failure);
- __ bind(L_success);
- } else {
- assert(res != noreg, "need result register");
-
- Label exit;
- __ bind(L_failure);
- __ xorptr(res, res);
- __ jmpb(exit);
-
- __ bind(L_success);
- __ movptr(res, 1);
- __ bind(exit);
- }
-}
-
#ifdef PRODUCT
#define BLOCK_COMMENT(str) /* nothing */
#else
@@ -926,7 +780,7 @@ void ShenandoahBarrierSetAssembler::store_c2(const MachNode* node, MacroAssemble
Register src, bool src_narrow,
Register tmp) {
- ShenandoahBarrierStubC2::store_pre(masm, node, tmp, dst, noreg, noreg, dst_narrow);
+ ShenandoahBarrierStubC2::store_pre(masm, node, dst, tmp, noreg, noreg, dst_narrow);
// Need to encode into tmp, because we cannot clobber src.
if (dst_narrow && !src_narrow) {
@@ -961,7 +815,7 @@ void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, Mac
assert_different_registers(oldval, tmp, addr.base(), addr.index());
assert_different_registers(newval, tmp, addr.base(), addr.index());
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp, addr, noreg, noreg, narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, tmp, noreg, noreg, narrow);
// CAS!
__ lock();
@@ -982,7 +836,7 @@ void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, Mac
void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAssembler* masm, Register newval, Address addr, Register tmp, bool narrow) {
assert_different_registers(newval, tmp, addr.base(), addr.index());
- ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp, addr, noreg, noreg, narrow);
+ ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, tmp, noreg, noreg, narrow);
if (narrow) {
__ xchgl(newval, addr);
diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp
index 592cbc42fe3..f608760ce42 100644
--- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp
+++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp
@@ -60,9 +60,6 @@ public:
void load_reference_barrier(MacroAssembler* masm, Register dst, Address src, DecoratorSet decorators);
- void cmpxchg_oop(MacroAssembler* masm,
- Register res, Address addr, Register oldval, Register newval,
- bool exchange, Register tmp1, Register tmp2);
virtual void arraycopy_prologue(MacroAssembler* masm, DecoratorSet decorators, BasicType type,
Register src, Register dst, Register count);
virtual void arraycopy_epilogue(MacroAssembler* masm, DecoratorSet decorators, BasicType type,
diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
index aa2195d0256..0ac0a8243d4 100644
--- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
@@ -4901,7 +4901,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
Register offset = rscratch1;
Label L_loop_search_receiver, L_loop_search_empty;
- Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update;
+ Label L_restart, L_found_recv, L_found_empty, L_count_update;
// The code here recognizes three major cases:
// A. Fastest: receiver found in the table
@@ -4931,21 +4931,20 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
// if (receiver(i) == recv) goto found_recv(i);
// }
//
- // // Fast: no receiver, but profile is full
+ // // Fast: no receiver, but profile is not full
// for (i = 0; i < receiver_count(); i++) {
// if (receiver(i) == null) goto found_null(i);
// }
- // goto polymorphic
+ //
+ // // Slow: profile is full, polymorphic case
+ // count++;
+ // return
//
// // Slow: try to install receiver
// found_null(i):
// CAS(&receiver(i), null, recv);
// goto restart
//
- // polymorphic:
- // count++;
- // return
- //
// found_recv(i):
// *receiver_count(i)++
//
@@ -4961,7 +4960,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
cmpptr(offset, end_receiver_offset);
jccb(Assembler::notEqual, L_loop_search_receiver);
- // Fast: no receiver, but profile is full
+ // Fast: no receiver, but profile is not full
movptr(offset, base_receiver_offset);
bind(L_loop_search_empty);
cmpptr(Address(mdp, offset, Address::times_ptr), NULL_WORD);
@@ -4969,9 +4968,13 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
addptr(offset, receiver_step);
cmpptr(offset, end_receiver_offset);
jccb(Assembler::notEqual, L_loop_search_empty);
- jmpb(L_polymorphic);
- // Slow: try to install receiver
+ // Slow: Receiver is not found and table is full.
+ // Increment polymorphic counter instead of receiver slot.
+ movptr(offset, poly_count_offset);
+ jmpb(L_count_update);
+
+ // Slowest: try to install receiver
bind(L_found_empty);
// Atomically swing receiver slot: null -> recv.
@@ -5023,17 +5026,11 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_
// and just restart the search from the beginning.
jmpb(L_restart);
- // Counter updates:
-
- // Increment polymorphic counter instead of receiver slot.
- bind(L_polymorphic);
- movptr(offset, poly_count_offset);
- jmpb(L_count_update);
-
// Found a receiver, convert its slot offset to corresponding count offset.
bind(L_found_recv);
addptr(offset, receiver_to_count_step);
+ // Finally, update the counter
bind(L_count_update);
addptr(Address(mdp, offset, Address::times_ptr), DataLayout::counter_increment);
}
diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp
index b64943fc4de..afd9c126a21 100644
--- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp
@@ -4904,6 +4904,11 @@ void StubGenerator::generate_compiler_stubs() {
StubRoutines::_intpoly_assign = generate_intpoly_assign();
}
+ if (UseIntPoly25519Intrinsics) {
+ StubRoutines::_intpoly_mult_25519 = generate_intpoly_mult_25519();
+ StubRoutines::_intpoly_square_25519 = generate_intpoly_square_25519();
+ }
+
if (UseMD5Intrinsics) {
StubRoutines::_md5_implCompress = generate_md5_implCompress(StubId::stubgen_md5_implCompress_id);
StubRoutines::_md5_implCompressMB = generate_md5_implCompress(StubId::stubgen_md5_implCompressMB_id);
diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp
index 360b0329d95..6e3da334f11 100644
--- a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp
@@ -496,6 +496,9 @@ class StubGenerator: public StubCodeGenerator {
address generate_intpoly_montgomeryMult_P256();
address generate_intpoly_assign();
+ address generate_intpoly_mult_25519();
+ address generate_intpoly_square_25519();
+
// SHA3 stubs
void generate_sha3_stubs();
diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp
index 13b1c942213..c35a2a1bba6 100644
--- a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp
@@ -631,6 +631,27 @@ address generate_kyberInverseNtt_avx512(StubGenerator *stubgen,
}
// Kyber multiply polynomials in the NTT domain.
+// Implements
+// static int implKyberNttMult(
+// short[] result, short[] ntta, short[] nttb, short[] zetas) {}
+//
+// The actual algorithm that is used here differs from the one in the Java
+// implementation, it uses Montgomery multiplications instead of Barrett
+// reduction, but the end result modulo MLKEM_Q is the same. This is the
+// Java equivalent of this intrinsic implementation:
+// static void implKyberNttMultJava(short[] result, short[] ntta, short[] nttb) {
+// for (int m = 0; m < ML_KEM_N / 2; m++) {
+// int a0 = ntta[2 * m];
+// int a1 = ntta[2 * m + 1];
+// int b0 = nttb[2 * m];
+// int b1 = nttb[2 * m + 1];
+// int r = montMul(a0, b0) +
+// montMul(montMul(a1, b1), MONT_ZETAS_FOR_NTT_MULT[m]);
+// result[2 * m] = (short) montMul(r, MONT_R_SQUARE_MOD_Q);
+// result[2 * m + 1] = (short) montMul(
+// (montMul(a0, b1) + montMul(a1, b0)), MONT_R_SQUARE_MOD_Q);
+// }
+// }
//
// result (short[256]) = c_rarg0
// ntta (short[256]) = c_rarg1
diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly25519.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly25519.cpp
new file mode 100644
index 00000000000..c7395220d49
--- /dev/null
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly25519.cpp
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "macroAssembler_x86.hpp"
+#include "stubGenerator_x86_64.hpp"
+
+#define __ _masm->
+
+const int32_t term = 19;
+const int32_t limbs = 5;
+const int32_t bpl = 51;
+const int32_t rem = 64 - bpl;
+const uint64_t MASK = 0x7FFFFFFFFFFFF;
+const uint64_t CARRY_ADD = 0x4000000000000;
+
+// Multiplication operation for polynomial arithmetic in Curve25519.
+//
+// This is the same algorithm as used in Java, except we use pseudo-Mersenne
+// reduction to reduce register pressure instead of using the full 10 columns
+// in Java.
+void multiply_25519_scalar(const Register aLimbs, const Register bLimbs, const Register rLimbs, Register c[], Register bArg, Register d, Register b, Register mask, MacroAssembler* _masm) {
+
+ for (int i = 0; i < limbs; i++) {
+ __ xorq(c[i], c[i]);
+ }
+ __ mov64(mask, MASK);
+ __ movq(bArg, bLimbs);
+
+ // Perform high/low multiplication with signed 5x51 bit limbs
+ for (int i = 0; i < limbs; i++) {
+ __ movq(b, Address(bArg, i * 8));
+ for (int j = 0; j < limbs; j++) {
+ __ movq(rax, Address(aLimbs, j * 8));
+ __ imulq(b); // rdx:rax = a * b
+ __ movq(d, rax);
+ __ andq(d, mask);
+ __ shrq(rax, bpl);
+ __ shlq(rdx, rem);
+ __ orq(rax, rdx);
+ // Fold in pseudo-Mersenne reduction
+ if ((i + j + 1) >= limbs) {
+ __ imulq(rax, rax, term);
+ }
+ if ((i + j) >= limbs) {
+ __ imulq(d, d, term);
+ }
+ __ addq(c[(i + j) % limbs], d);
+ __ addq(c[(i + j + 1) % limbs], rax);
+ }
+ }
+
+ // Carry-add with reduction from high limb
+ Register carry = bArg;
+ __ mov64(mask, CARRY_ADD);
+ __ movq(carry, mask);
+
+ // Limb 3
+ __ addq(carry, c[3]);
+ __ sarq(carry, bpl);
+ __ addq(c[4], carry);
+ __ shlq(carry, bpl);
+ __ subq(c[3], carry);
+
+ // Limb 4
+ __ movq(carry, mask);
+ __ addq(carry, c[4]);
+ __ sarq(carry, bpl);
+
+ // Reduce high order limb and fold back into low order limb
+ __ mov64(rax, term);
+ __ imulq(carry);
+ __ addq(c[0], rax);
+
+ __ shlq(carry, bpl);
+ __ subq(c[4], carry);
+
+ // Limbs 0 - 3
+ for (int i = 0; i < (limbs - 1); i++) {
+ __ movq(carry, mask);
+ __ addq(carry, c[i]);
+ __ sarq(carry, bpl);
+ __ addq(c[i + 1], carry);
+ __ shlq(carry, bpl);
+ __ subq(c[i], carry);
+ }
+
+ __ pop_ppx(rdx);
+
+ for (int i = 0; i < limbs; i++) {
+ __ movq(Address(rLimbs, i * 8), c[i]);
+ }
+}
+
+// Squaring operation for polynomial arithmetic in Curve25519.
+//
+// This is the same algorithm as used in Java, except we use pseudo-Mersenne
+// reduction to reduce register pressure instead of using the full 10 columns
+// in Java.
+void square_25519_scalar(const Register aLimbs, const Register rLimbs, Register c[], Register aArg, Register d, Register carry, Register mask, MacroAssembler* _masm) {
+
+ for (int i = 0; i < limbs; i++) {
+ __ xorq(c[i], c[i]);
+ }
+ __ mov64(mask, MASK);
+
+ // Perform high/low multiplication with signed 5x51 bit limbs
+ for (int i = 0; i < limbs; i++) {
+ __ movq(aArg, Address(aLimbs, i * 8));
+ __ movq(rax, aArg);
+ __ imulq(aArg); // rdx:rax = a[j] * a[i]
+ __ movq(d, rax);
+ __ andq(d, mask);
+ __ shrq(rax, bpl);
+ __ shlq(rdx, rem);
+ __ orq(rax, rdx); // rax = dd
+ if ((i * 2 + 1) >= limbs) {
+ __ imulq(rax, rax, term);
+ }
+ if ((i * 2) >= limbs) {
+ __ imulq(d, d, term);
+ }
+ __ addq(c[(i * 2) % limbs], d);
+ __ addq(c[(i * 2 + 1) % limbs], rax);
+ for (int j = i + 1; j < limbs; j++) {
+ __ movq(rax, Address(aLimbs, j * 8));
+ __ imulq(aArg); // rdx:rax = a * a
+ __ movq(d, rax);
+ __ andq(d, mask);
+ __ shlq(d, 1);
+ __ shrq(rax, bpl);
+ __ shlq(rdx, rem);
+ __ orq(rax, rdx); // rax = dd
+ __ shlq(rax, 1);
+ if ((j + i + 1) >= limbs) {
+ __ imulq(rax, rax, term);
+ }
+ if ((j + i) >= limbs) {
+ __ imulq(d, d, term);
+ }
+ __ addq(c[(i + j) % limbs], d);
+ __ addq(c[(i + j + 1) % limbs], rax);
+ }
+ }
+
+ // Carry-add with reduction from high limb
+ // Limb 3
+ __ mov64(mask, CARRY_ADD);
+ __ movq(carry, mask);
+ __ addq(carry, c[3]);
+ __ sarq(carry, bpl);
+ __ addq(c[4], carry);
+ __ shlq(carry, bpl);
+ __ subq(c[3], carry);
+
+ // Limb 4
+ __ movq(carry, mask);
+ __ addq(carry, c[4]);
+ __ sarq(carry, bpl);
+
+ // Reduce high order limb and fold back into low order limb
+ __ mov64(rax, term);
+ __ imulq(carry);
+ __ addq(c[0], rax);
+
+ __ shlq(carry, bpl);
+ __ subq(c[4], carry);
+
+ // Limbs 0 - 3
+ for (int i = 0; i < (limbs - 1); i++) {
+ __ movq(carry, mask);
+ __ addq(carry, c[i]);
+ __ sarq(carry, bpl);
+ __ addq(c[i + 1], carry);
+ __ shlq(carry, bpl);
+ __ subq(c[i], carry);
+ }
+
+ __ pop_ppx(rdx);
+
+ for (int i = 0; i < limbs; i++) {
+ __ movq(Address(rLimbs, i * 8), c[i]);
+ }
+}
+
+address StubGenerator::generate_intpoly_mult_25519() {
+ StubId stub_id = StubId::stubgen_intpoly_mult_25519_id;
+ int entry_count = StubInfo::entry_count(stub_id);
+ assert(entry_count == 1, "sanity check");
+ address start = load_archive_data(stub_id);
+ if (start != nullptr) {
+ return start;
+ }
+ __ align(CodeEntryAlignment);
+ StubCodeMark mark(this, stub_id);
+ start = __ pc();
+ __ enter();
+
+ // Register Map
+ const Register aLimbs = c_rarg0; // rdi | rcx
+ const Register bLimbs = c_rarg1; // rsi | rdx
+ const Register rLimbs = c_rarg2; // rdx | r8
+
+ Register c[] = {r9, r10, r11, r12, r13};
+ Register bArg = r14;
+ Register d = r15;
+ Register b = rbp;
+ Register mask = rbx;
+
+ __ push_ppx(rbp);
+ __ push_ppx(rbx);
+ __ push_ppx(r12);
+ __ push_ppx(r13);
+ __ push_ppx(r14);
+ __ push_ppx(r15);
+ __ push_ppx(rdx);
+
+ multiply_25519_scalar(aLimbs, bLimbs, rLimbs, c, bArg, d, b, mask, _masm);
+
+ // __ pop_ppx(rdx); // restored in the helper already
+ __ pop_ppx(r15);
+ __ pop_ppx(r14);
+ __ pop_ppx(r13);
+ __ pop_ppx(r12);
+ __ pop_ppx(rbx);
+ __ pop_ppx(rbp);
+
+ __ leave();
+ __ ret(0);
+
+ // Record the stub entry and end
+ store_archive_data(stub_id, start, __ pc());
+
+ return start;
+}
+
+address StubGenerator::generate_intpoly_square_25519() {
+ StubId stub_id = StubId::stubgen_intpoly_square_25519_id;
+ int entry_count = StubInfo::entry_count(stub_id);
+ assert(entry_count == 1, "sanity check");
+ address start = load_archive_data(stub_id);
+ if (start != nullptr) {
+ return start;
+ }
+ __ align(CodeEntryAlignment);
+ StubCodeMark mark(this, stub_id);
+ start = __ pc();
+ __ enter();
+
+ // Register Map
+ const Register aLimbs = c_rarg0; // rdi | rcx
+ const Register rLimbs = c_rarg1; // rsi | rdx
+ Register c[] = {r9, r10, r11, r12, r13};
+ Register aArg = r14;
+ Register d = r15;
+ Register carry = rbp;
+ Register mask = rbx;
+
+ __ push_ppx(rbp);
+ __ push_ppx(rbx);
+ __ push_ppx(r12);
+ __ push_ppx(r13);
+ __ push_ppx(r14);
+ __ push_ppx(r15);
+ __ push_ppx(rdx);
+
+ square_25519_scalar(aLimbs, rLimbs, c, aArg, d, carry, mask, _masm);
+
+ // __ pop_ppx(rdx); // restored in the helper already
+ __ pop_ppx(r15);
+ __ pop_ppx(r14);
+ __ pop_ppx(r13);
+ __ pop_ppx(r12);
+ __ pop_ppx(rbx);
+ __ pop_ppx(rbp);
+
+ __ leave();
+ __ ret(0);
+
+ // Record the stub entry and end
+ store_archive_data(stub_id, start, __ pc());
+
+ return start;
+}
+#undef __
diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp
index 308a8042993..76b6fa97fa5 100644
--- a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, 2025, Intel Corporation. All rights reserved.
+ * Copyright (c) 2024, 2026, Intel Corporation. All rights reserved.
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -676,7 +676,7 @@ address StubGenerator::generate_intpoly_assign() {
// KNOWN Lengths:
// MontgomeryIntPolynP256: 5 = 4 + 1
// IntegerPolynomial1305: 5 = 4 + 1
- // IntegerPolynomial25519: 10 = 8 + 2
+ // IntegerPolynomial25519: 5 = 4 + 1
// IntegerPolynomialP256: 10 = 8 + 2
// Curve25519OrderField: 10 = 8 + 2
// Curve25519OrderField: 10 = 8 + 2
diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp
index 4cdcb1770bb..2ca1c172542 100644
--- a/src/hotspot/cpu/x86/vm_version_x86.cpp
+++ b/src/hotspot/cpu/x86/vm_version_x86.cpp
@@ -1407,6 +1407,10 @@ void VM_Version::get_processor_features() {
FLAG_SET_DEFAULT(UseIntPolyIntrinsics, false);
}
+ if (FLAG_IS_DEFAULT(UseIntPoly25519Intrinsics)) {
+ UseIntPoly25519Intrinsics = true;
+ }
+
if (FLAG_IS_DEFAULT(UseMultiplyToLenIntrinsic)) {
UseMultiplyToLenIntrinsic = true;
}
diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad
index ab39692b44b..b3dd1a0812b 100644
--- a/src/hotspot/cpu/x86/x86.ad
+++ b/src/hotspot/cpu/x86/x86.ad
@@ -13959,6 +13959,7 @@ instruct orL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr)
// Use any_RegP to match R15 (TLS register) without spilling.
instruct orL_rReg_castP2X(rRegL dst, any_RegP src, rFlagsReg cr) %{
+ predicate(!UseAPX);
match(Set dst (OrL dst (CastP2X src)));
effect(KILL cr);
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
@@ -13971,6 +13972,7 @@ instruct orL_rReg_castP2X(rRegL dst, any_RegP src, rFlagsReg cr) %{
%}
instruct orL_rReg_castP2X_ndd(rRegL dst, any_RegP src1, any_RegP src2, rFlagsReg cr) %{
+ predicate(UseAPX);
match(Set dst (OrL src1 (CastP2X src2)));
effect(KILL cr);
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp
index ba48f2b4efc..ad1f384fa32 100644
--- a/src/hotspot/os/linux/os_linux.cpp
+++ b/src/hotspot/os/linux/os_linux.cpp
@@ -2183,6 +2183,10 @@ void os::print_os_info(outputStream* st) {
st->cr();
}
+ if (os::Linux::print_numa_info(st)) {
+ st->cr();
+ }
+
VM_Version::print_platform_virtualization_info(st);
os::Linux::print_steal_info(st);
@@ -2622,6 +2626,97 @@ bool os::Linux::print_container_info(outputStream* st) {
return true;
}
+#define SYS_DEVICES_NODE "/sys/devices/system/node"
+
+static size_t read_sysfs_file(const char* path, char* buf, size_t sz) {
+ FILE* f = os::fopen(path, "r");
+ if (f == nullptr) return 0;
+ size_t n = fread(buf, 1, sz - 1, f);
+ fclose(f);
+ buf[n] = '\0';
+ while (n > 0 && (buf[n-1] == '\n' || buf[n-1] == '\r')) buf[--n] = '\0';
+ return n;
+}
+
+static void print_numa_memory_info(outputStream* st, int node) {
+ char path[256];
+ char line[256];
+ long long mem_total = -1;
+ long long mem_free = -1;
+ os::snprintf_checked(path, sizeof(path), SYS_DEVICES_NODE "/node%d/meminfo", node);
+ FILE* f = os::fopen(path, "r");
+ if (f == nullptr) {
+ return;
+ }
+
+ while (fgets(line, sizeof(line), f) != nullptr) {
+ long long mval;
+ if (sscanf(line, "Node %*d MemTotal: %lld kB", &mval) == 1) mem_total = mval;
+ if (sscanf(line, "Node %*d MemFree: %lld kB", &mval) == 1) mem_free = mval;
+ }
+ fclose(f);
+
+ if (mem_total >= 0) { st->print_cr("mem size: %lld kB", mem_total); }
+ if (mem_free >= 0) { st->print_cr("mem free: %lld kB", mem_free); }
+}
+
+static void print_numa_cpu_list(outputStream* st, int node) {
+ char path[256];
+ char buf[1024];
+ os::snprintf_checked(path, sizeof(path), SYS_DEVICES_NODE "/node%d/cpulist", node);
+ if (read_sysfs_file(path, buf, sizeof(buf)) > 0) {
+ st->print_cr("cpus: %s", buf);
+ } else {
+ st->print_cr("cpus: (unavailable)");
+ }
+}
+
+bool os::Linux::print_numa_info(outputStream* st) {
+ if (!UseNUMA) {
+ // If NUMA optimizations are not enabled we don't print anything
+ return false;
+ }
+
+ char buf[1024];
+ if (read_sysfs_file("/sys/devices/system/node/online", buf, sizeof(buf)) > 0) {
+ st->print_cr("NUMA nodes online: %s", buf);
+ } else {
+ return false;
+ }
+
+ bool first = true;
+ int node_count = 0;
+
+ if (nindex_to_node() == nullptr) {
+ return false;
+ }
+
+ for (int node: *nindex_to_node()) {
+ char nodepath[256];
+ os::snprintf_checked(nodepath, sizeof(nodepath), SYS_DEVICES_NODE "/node%d", node);
+ DIR* currd = os::opendir(nodepath);
+ if (currd == nullptr) continue;
+ if (first) {
+ st->cr();
+ first = false;
+ }
+ os::closedir(currd);
+
+ st->print_cr("NUMA node %d", node);
+ StreamIndentor si(st);
+ print_numa_cpu_list(st, node);
+ print_numa_memory_info(st, node);
+ node_count++;
+ }
+
+ if (node_count == 0) {
+ return false;
+ }
+
+ st->print_cr("Total NUMA node count: %d", node_count);
+ return true;
+}
+
void os::Linux::print_steal_info(outputStream* st) {
if (has_initial_tick_info) {
CPUPerfTicks pticks;
diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp
index 41b5afbf5c3..51a10be1e5d 100644
--- a/src/hotspot/os/linux/os_linux.hpp
+++ b/src/hotspot/os/linux/os_linux.hpp
@@ -77,6 +77,7 @@ class os::Linux {
static void print_proc_sys_info(outputStream* st);
static bool print_ld_preload_file(outputStream* st);
static void print_uptime_info(outputStream* st);
+ static bool print_numa_info(outputStream* st);
public:
struct CPUPerfTicks {
diff --git a/src/hotspot/os/posix/os_posix.cpp b/src/hotspot/os/posix/os_posix.cpp
index 1fb2a248bec..1eee76b4667 100644
--- a/src/hotspot/os/posix/os_posix.cpp
+++ b/src/hotspot/os/posix/os_posix.cpp
@@ -167,11 +167,11 @@ void os::check_core_dump_prerequisites(char* buffer, size_t bufferSize, bool che
}
}
-bool os::committed_in_range(address start, size_t size, address& committed_start, size_t& committed_size) {
+bool os::first_resident_in_range(address start, size_t size, address& resident_start, size_t& resident_size) {
#ifdef _AIX
- committed_start = start;
- committed_size = size;
+ resident_start = start;
+ resident_size = size;
return true;
#else
@@ -188,10 +188,10 @@ bool os::committed_in_range(address start, size_t size, address& committed_start
assert(is_aligned(start, page_sz), "Start address must be page aligned");
assert(is_aligned(size, page_sz), "Size must be page aligned");
- committed_start = nullptr;
+ resident_start = nullptr;
int loops = checked_cast((pages + stripe - 1) / stripe);
- int committed_pages = 0;
+ int resident_pages = 0;
address loop_base = start;
bool found_range = false;
@@ -210,7 +210,7 @@ bool os::committed_in_range(address start, size_t size, address& committed_start
// During shutdown, some memory goes away without properly notifying NMT,
// E.g. ConcurrentGCThread/WatcherThread can exit without deleting thread object.
- // Bailout and return as not committed for now.
+ // Bailout and return as not resident for now.
if (mincore_return_value == -1 && errno == ENOMEM) {
return false;
}
@@ -224,32 +224,32 @@ bool os::committed_in_range(address start, size_t size, address& committed_start
assert(mincore_return_value == 0, "Range must be valid");
// Process this stripe
for (uintx vecIdx = 0; vecIdx < pages_to_query; vecIdx ++) {
- if ((vec[vecIdx] & 0x01) == 0) { // not committed
+ if ((vec[vecIdx] & 0x01) == 0) { // not resident
// End of current contiguous region
- if (committed_start != nullptr) {
+ if (resident_start != nullptr) {
found_range = true;
break;
}
- } else { // committed
+ } else { // resident
// Start of region
- if (committed_start == nullptr) {
- committed_start = loop_base + page_sz * vecIdx;
+ if (resident_start == nullptr) {
+ resident_start = loop_base + page_sz * vecIdx;
}
- committed_pages ++;
+ resident_pages ++;
}
}
loop_base += pages_to_query * page_sz;
}
- if (committed_start != nullptr) {
- assert(committed_pages > 0, "Must have committed region");
- assert(committed_pages <= int(size / page_sz), "Can not commit more than it has");
- assert(committed_start >= start && committed_start < start + size, "Out of range");
- committed_size = page_sz * committed_pages;
+ if (resident_start != nullptr) {
+ assert(resident_pages > 0, "Must have a resident region");
+ assert(resident_pages <= int(size / page_sz), "Resident size exceeds region size");
+ assert(resident_start >= start && resident_start < start + size, "Out of range");
+ resident_size = page_sz * resident_pages;
return true;
} else {
- assert(committed_pages == 0, "Should not have committed region");
+ assert(resident_pages == 0, "Should not have a resident region");
return false;
}
#endif
diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp
index 9a987bf3762..d00babef40f 100644
--- a/src/hotspot/os/windows/os_windows.cpp
+++ b/src/hotspot/os/windows/os_windows.cpp
@@ -243,6 +243,16 @@ static LPVOID virtualAllocExNuma(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSiz
return result;
}
+void* os::win32::lookup_kernelbase_symbol(const char* name) {
+ // Pass a small ebuf so dll_load logs failures, but don't use it here to avoid redundancy.
+ char ebuf[1024];
+ static void* const handle = os::dll_load("KernelBase", ebuf, sizeof(ebuf));
+ if (handle == nullptr) {
+ return nullptr;
+ }
+ return os::dll_lookup(handle, name);
+}
+
// Logging wrapper for MapViewOfFileEx
static LPVOID mapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap, LPVOID lpBaseAddress) {
@@ -465,36 +475,63 @@ void os::current_stack_base_and_size(address* stack_base, size_t* stack_size) {
*stack_size = size;
}
-bool os::committed_in_range(address start, size_t size, address& committed_start, size_t& committed_size) {
- MEMORY_BASIC_INFORMATION minfo;
- committed_start = nullptr;
- committed_size = 0;
- address top = start + size;
- const address start_addr = start;
- while (start < top) {
- VirtualQuery(start, &minfo, sizeof(minfo));
- if ((minfo.State & MEM_COMMIT) == 0) { // not committed
- if (committed_start != nullptr) {
- break;
- }
- } else { // committed
- if (committed_start == nullptr) {
- committed_start = start;
- }
- size_t offset = start - (address)minfo.BaseAddress;
- committed_size += minfo.RegionSize - offset;
- }
- start = (address)minfo.BaseAddress + minfo.RegionSize;
- }
+bool os::first_resident_in_range(address start, size_t size, address& resident_start, size_t& resident_size) {
+ constexpr size_t stripe = 1024; // query this many pages each time
+ PSAPI_WORKING_SET_EX_INFORMATION wsinfo[stripe];
- if (committed_start == nullptr) {
- assert(committed_size == 0, "Sanity");
- return false;
- } else {
- assert(committed_start >= start_addr && committed_start < top, "Out of range");
- // current region may go beyond the limit, trim to the limit
- committed_size = MIN2(committed_size, size_t(top - committed_start));
+ size_t page_sz = os::vm_page_size();
+ uintx pages_left = size / page_sz;
+
+ assert(is_aligned(start, page_sz), "Start address must be page aligned");
+ assert(is_aligned(size, page_sz), "Size must be page aligned");
+
+ resident_start = nullptr;
+
+ uintx loops = (pages_left + stripe - 1) / stripe;
+ uintx resident_pages = 0;
+ address pos = start;
+ bool found_range = false;
+
+ for (uintx index = 0; index < loops && !found_range; index++) {
+ assert(pages_left > 0, "Nothing to do");
+ uintx pages_to_query = MIN2(pages_left, stripe);
+ pages_left -= pages_to_query;
+
+ for (uintx i = 0; i < pages_to_query; i++) {
+ wsinfo[i].VirtualAddress = (PVOID)(pos + i * page_sz);
+ }
+
+ BOOL success = QueryWorkingSetEx(GetCurrentProcess(), wsinfo, pages_to_query * sizeof(PSAPI_WORKING_SET_EX_INFORMATION));
+ if (!success) {
+ return false;
+ }
+
+ for (uintx i = 0; i < pages_to_query; i++) {
+ if (wsinfo[i].VirtualAttributes.Valid == 0) {
+ if (resident_start != nullptr) {
+ found_range = true;
+ break;
+ }
+ // Still searching for start of resident region
+ } else {
+ if (resident_start == nullptr) {
+ // Found first resident page in region
+ resident_start = pos + i * page_sz;
+ }
+ resident_pages++;
+ }
+ }
+ pos += pages_to_query * page_sz;
+ }
+ if (resident_start != nullptr) {
+ assert(resident_pages > 0, "Must have a resident region");
+ assert(resident_pages <= size / page_sz, "Resident size exceeds region size");
+ assert(resident_start >= start && resident_start < start + size, "Out of range");
+ resident_size = page_sz * resident_pages;
return true;
+ } else {
+ assert(resident_pages == 0, "Should not have a resident region");
+ return false;
}
}
@@ -3223,9 +3260,9 @@ char* os::map_memory_to_file(char* base, size_t size, int fd) {
assert(fd != -1, "File descriptor is not valid");
HANDLE fh = (HANDLE)_get_osfhandle(fd);
- HANDLE fileMapping = CreateFileMapping(fh, nullptr, PAGE_READWRITE,
+ HANDLE file_mapping = CreateFileMapping(fh, nullptr, PAGE_READWRITE,
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF), nullptr);
- if (fileMapping == nullptr) {
+ if (file_mapping == nullptr) {
if (GetLastError() == ERROR_DISK_FULL) {
vm_exit_during_initialization(err_msg("Could not allocate sufficient disk space for Java heap"));
}
@@ -3236,9 +3273,9 @@ char* os::map_memory_to_file(char* base, size_t size, int fd) {
return nullptr;
}
- LPVOID addr = mapViewOfFileEx(fileMapping, FILE_MAP_WRITE, 0, 0, size, base);
+ LPVOID addr = mapViewOfFileEx(file_mapping, FILE_MAP_WRITE, 0, 0, size, base);
- CloseHandle(fileMapping);
+ CloseHandle(file_mapping);
return (char*)addr;
}
@@ -3251,40 +3288,75 @@ char* os::replace_existing_mapping_with_file_mapping(char* base, size_t size, in
return map_memory_to_file(base, size, fd);
}
+// VirtualAlloc2 / MapViewOfFile3 (Windows 1803+). Resolved in os::init_2() via lookup_kernelbase_symbol.
+os::win32::VirtualAlloc2Fn os::win32::VirtualAlloc2 = nullptr;
+
+os::win32::MapViewOfFile3Fn os::win32::MapViewOfFile3 = nullptr;
+
+static bool is_VirtualAlloc2_supported() {
+ return os::win32::VirtualAlloc2 != nullptr;
+}
+
+static bool is_MapViewOfFile3_supported() {
+ return os::win32::MapViewOfFile3 != nullptr;
+}
+
// Multiple threads can race in this code but it's not possible to unmap small sections of
// virtual space to get requested alignment, like posix-like os's.
// Windows prevents multiple thread from remapping over each other so this loop is thread-safe.
-static char* map_or_reserve_memory_aligned(size_t size, size_t alignment, int file_desc, MemTag mem_tag) {
+static char* reserve_memory_aligned(size_t size, size_t alignment, MemTag mem_tag) {
assert(is_aligned(alignment, os::vm_allocation_granularity()),
- "Alignment must be a multiple of allocation granularity (page size)");
+ "Alignment must be a multiple of allocation granularity");
assert(is_aligned(size, os::vm_allocation_granularity()),
- "Size must be a multiple of allocation granularity (page size)");
+ "Size must be a multiple of allocation granularity");
size_t extra_size = size + alignment;
assert(extra_size >= size, "overflow, size is too large to allow alignment");
char* aligned_base = nullptr;
- static const int max_attempts = 20;
+ constexpr int max_attempts = 20;
for (int attempt = 0; attempt < max_attempts && aligned_base == nullptr; attempt ++) {
- char* extra_base = file_desc != -1 ? os::map_memory_to_file(extra_size, file_desc, mem_tag) :
- os::reserve_memory(extra_size, mem_tag);
+ char* extra_base = os::reserve_memory(extra_size, mem_tag);
if (extra_base == nullptr) {
return nullptr;
}
- // Do manual alignment
aligned_base = align_up(extra_base, alignment);
+ os::release_memory(extra_base, extra_size);
- if (file_desc != -1) {
- os::unmap_memory(extra_base, extra_size);
- } else {
- os::release_memory(extra_base, extra_size);
+ // A racing thread may have taken this region instead of us, which is why we loop and retry.
+ aligned_base = os::attempt_reserve_memory_at(aligned_base, size, mem_tag);
+ }
+
+ assert(aligned_base != nullptr,
+ "Did not manage to reserve after %d attempts (size %zu, alignment %zu)", max_attempts, size, alignment);
+
+ return aligned_base;
+}
+
+// Similar to reserve_memory_aligned, other reservation/mapping requests can race with this function.
+static char* map_memory_aligned(size_t size, size_t alignment, int file_desc, MemTag mem_tag) {
+ assert(is_aligned(alignment, os::vm_allocation_granularity()),
+ "Alignment must be a multiple of allocation granularity");
+ assert(is_aligned(size, os::vm_allocation_granularity()),
+ "Size must be a multiple of allocation granularity");
+
+ size_t extra_size = size + alignment;
+ assert(extra_size >= size, "overflow, size is too large to allow alignment");
+
+ char* aligned_base = nullptr;
+ constexpr int max_attempts = 20;
+
+ for (int attempt = 0; attempt < max_attempts && aligned_base == nullptr; attempt ++) {
+ char* extra_base = os::map_memory_to_file(extra_size, file_desc, mem_tag);
+ if (extra_base == nullptr) {
+ return nullptr;
}
+ aligned_base = align_up(extra_base, alignment);
+ os::unmap_memory(extra_base, extra_size);
- // Attempt to map, into the just vacated space, the slightly smaller aligned area.
- // Which may fail, hence the loop.
- aligned_base = file_desc != -1 ? os::attempt_map_memory_to_file_at(aligned_base, size, file_desc, mem_tag) :
- os::attempt_reserve_memory_at(aligned_base, size, mem_tag);
+ // A racing thread may have taken this region instead of us, which is why we loop and retry.
+ aligned_base = os::attempt_map_memory_to_file_at(aligned_base, size, file_desc, mem_tag);
}
assert(aligned_base != nullptr,
@@ -3293,6 +3365,84 @@ static char* map_or_reserve_memory_aligned(size_t size, size_t alignment, int fi
return aligned_base;
}
+// MapViewOfFile3 supports alignment natively.
+static char* map_memory_aligned_va2(size_t size, size_t alignment, int file_desc, MemTag mem_tag) {
+ assert(file_desc != -1, "File descriptor should not be -1");
+ assert(is_aligned(alignment, os::vm_allocation_granularity()),
+ "Alignment must be a multiple of allocation granularity");
+ assert(is_aligned(size, os::vm_allocation_granularity()),
+ "Size must be a multiple of allocation granularity");
+
+ MEM_ADDRESS_REQUIREMENTS requirements = {0};
+ requirements.Alignment = alignment;
+
+ MEM_EXTENDED_PARAMETER param = {0};
+ param.Type = MemExtendedParameterAddressRequirements;
+ param.Pointer = &requirements;
+
+ char* aligned_base = nullptr;
+
+ // File-backed aligned mapping.
+ HANDLE fh = (HANDLE)_get_osfhandle(file_desc);
+ HANDLE file_mapping = CreateFileMapping(fh, nullptr, PAGE_READWRITE,(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF), nullptr);
+ DWORD err = GetLastError();
+ if (file_mapping != nullptr) {
+ aligned_base = (char*)os::win32::MapViewOfFile3(
+ file_mapping,
+ GetCurrentProcess(),
+ nullptr, // let the system choose an aligned address
+ 0, // offset
+ size,
+ 0, // no special allocation type flags
+ PAGE_READWRITE,
+ ¶m, 1);
+ err = GetLastError();
+ CloseHandle(file_mapping);
+ }
+
+ if (aligned_base != nullptr) {
+ assert(is_aligned(aligned_base, alignment), "Result must be aligned");
+ MemTracker::record_virtual_memory_reserve_and_commit(aligned_base, size, CALLER_PC, mem_tag);
+ return aligned_base;
+ }
+ log_trace(os)("Aligned allocation via MapViewOfFile3 failed, falling back to retry loop. GetLastError->%lu.", err);
+ return map_memory_aligned(size, alignment, file_desc, mem_tag);
+}
+
+// VirtualAlloc2 supports alignment natively.
+static char* reserve_memory_aligned_va2(size_t size, size_t alignment, MemTag mem_tag) {
+ assert(is_aligned(alignment, os::vm_allocation_granularity()),
+ "Alignment must be a multiple of allocation granularity");
+ assert(is_aligned(size, os::vm_allocation_granularity()),
+ "Size must be a multiple of allocation granularity");
+
+ MEM_ADDRESS_REQUIREMENTS requirements = {0};
+ requirements.Alignment = alignment;
+
+ MEM_EXTENDED_PARAMETER param = {0};
+ param.Type = MemExtendedParameterAddressRequirements;
+ param.Pointer = &requirements;
+
+ char* aligned_base = nullptr;
+
+ // Anonymous aligned reservation.
+ aligned_base = (char*)os::win32::VirtualAlloc2(
+ GetCurrentProcess(),
+ nullptr, // let the system choose an aligned address
+ size,
+ MEM_RESERVE,
+ PAGE_READWRITE,
+ ¶m, 1);
+
+ if (aligned_base != nullptr) {
+ assert(is_aligned(aligned_base, alignment), "Result must be aligned");
+ MemTracker::record_virtual_memory_reserve(aligned_base, size, CALLER_PC, mem_tag);
+ return aligned_base;
+ }
+ log_trace(os)("Aligned allocation via VirtualAlloc2 failed, falling back to retry loop. GetLastError->%lu.", GetLastError());
+ return reserve_memory_aligned(size, alignment, mem_tag);
+}
+
size_t os::commit_memory_limit() {
BOOL is_in_job_object = false;
BOOL res = IsProcessInJob(GetCurrentProcess(), nullptr, &is_in_job_object);
@@ -3340,11 +3490,17 @@ size_t os::reserve_memory_limit() {
char* os::reserve_memory_aligned(size_t size, size_t alignment, MemTag mem_tag, bool exec) {
// exec can be ignored
- return map_or_reserve_memory_aligned(size, alignment, -1/* file_desc */, mem_tag);
+ if (is_VirtualAlloc2_supported()) {
+ return reserve_memory_aligned_va2(size, alignment, mem_tag);
+ }
+ return reserve_memory_aligned(size, alignment, mem_tag);
}
char* os::map_memory_to_file_aligned(size_t size, size_t alignment, int fd, MemTag mem_tag) {
- return map_or_reserve_memory_aligned(size, alignment, fd, mem_tag);
+ if (is_MapViewOfFile3_supported()) {
+ return map_memory_aligned_va2(size, alignment, fd, mem_tag);
+ }
+ return map_memory_aligned(size, alignment, fd, mem_tag);
}
char* os::pd_reserve_memory(size_t bytes, bool exec) {
@@ -4561,21 +4717,21 @@ jint os::init_2(void) {
// Lookup SetThreadDescription - the docs state we must use runtime-linking of
// kernelbase.dll, so that is what we do.
- HINSTANCE _kernelbase = LoadLibrary(TEXT("kernelbase.dll"));
- if (_kernelbase != nullptr) {
- _SetThreadDescription =
- reinterpret_cast(
- GetProcAddress(_kernelbase,
- "SetThreadDescription"));
+ _SetThreadDescription = reinterpret_cast(
+ os::win32::lookup_kernelbase_symbol("SetThreadDescription"));
#ifdef ASSERT
- _GetThreadDescription =
- reinterpret_cast(
- GetProcAddress(_kernelbase,
- "GetThreadDescription"));
+ _GetThreadDescription = reinterpret_cast(
+ os::win32::lookup_kernelbase_symbol("GetThreadDescription"));
#endif
- }
log_info(os, thread)("The SetThreadDescription API is%s available.", _SetThreadDescription == nullptr ? " not" : "");
+ // Prepare KernelBase APIs (VirtualAlloc2, MapViewOfFile3) if available (Windows version 1803).
+ os::win32::VirtualAlloc2 = reinterpret_cast(
+ os::win32::lookup_kernelbase_symbol("VirtualAlloc2"));
+ os::win32::MapViewOfFile3 = reinterpret_cast(
+ os::win32::lookup_kernelbase_symbol("MapViewOfFile3"));
+ log_debug(os)("VirtualAlloc2 is%s available.", os::win32::VirtualAlloc2 == nullptr ? " not" : "");
+ log_debug(os)("MapViewOfFile3 is%s available.", os::win32::MapViewOfFile3 == nullptr ? " not" : "");
return JNI_OK;
}
diff --git a/src/hotspot/os/windows/os_windows.hpp b/src/hotspot/os/windows/os_windows.hpp
index d4a7d51c59b..5ebc80c817b 100644
--- a/src/hotspot/os/windows/os_windows.hpp
+++ b/src/hotspot/os/windows/os_windows.hpp
@@ -109,6 +109,19 @@ class os::win32 {
// load dll from Windows system directory or Windows directory
static HINSTANCE load_Windows_dll(const char* name, char *ebuf, int ebuflen);
+ // Resolve a symbol from KernelBase.dll, returns nullptr if not found.
+ static void* lookup_kernelbase_symbol(const char* name);
+
+ // VirtualAlloc2 (since Windows version 1803)
+ // Resolved from KernelBase during os::init_2() or nullptr if unavailable.
+ typedef PVOID (WINAPI *VirtualAlloc2Fn)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG);
+ static VirtualAlloc2Fn VirtualAlloc2;
+
+ // MapViewOfFile3 (since Windows version 1803)
+ // Resolved from KernelBase during os::init_2() or nullptr if unavailable.
+ typedef PVOID (WINAPI *MapViewOfFile3Fn)(HANDLE, HANDLE, PVOID, ULONG64, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG);
+ static MapViewOfFile3Fn MapViewOfFile3;
+
private:
static void initialize_performance_counter();
diff --git a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp
index 253f460dca3..a3bd1bfa870 100644
--- a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp
+++ b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Rivos Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -249,7 +249,6 @@ void RiscvHwprobe::add_features_from_query_result() {
if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVFH)) {
VM_Version::ext_Zvfh.enable_feature();
}
-#ifndef PRODUCT
if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKNED) &&
is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKNHB) &&
is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKB) &&
@@ -259,7 +258,6 @@ void RiscvHwprobe::add_features_from_query_result() {
if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKG)) {
VM_Version::ext_Zvkg.enable_feature();
}
-#endif
// ====== non-extensions ======
//
diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp
index 7985c62d67b..bfaa1d6644c 100644
--- a/src/hotspot/share/cds/archiveUtils.cpp
+++ b/src/hotspot/share/cds/archiveUtils.cpp
@@ -303,7 +303,8 @@ public:
AllocGapNode* node = allocate_node(gap, Empty{});
insert(gap, node);
- log_trace(aot, alloc)("adding a gap of %zu bytes @ %p (total = %zu) in %zu blocks", gap_bytes, gap_bottom, _total_gap_bytes, size());
+ log_trace(aot, alloc)("adding a gap of %zu bytes @ %p (total = %zu, used = %zu) in %zu blocks",
+ gap_bytes, gap_bottom, _total_gap_bytes, _total_gap_bytes_used, size());
return gap_bytes;
}
@@ -325,29 +326,25 @@ public:
remove(node);
- precond(_total_gap_bytes >= num_bytes);
- _total_gap_bytes -= num_bytes;
_total_gap_bytes_used += num_bytes;
_total_gap_allocs++;
DEBUG_ONLY(node = nullptr); // Don't use it anymore!
precond(gap_bytes >= num_bytes);
if (gap_bytes > num_bytes) {
- gap_bytes -= num_bytes;
- gap_bottom += num_bytes;
-
- AllocGap gap(gap_bytes, gap_bottom); // constructor checks alignment
+ AllocGap gap(gap_bytes - num_bytes, gap_bottom + num_bytes); // constructor checks alignment
AllocGapNode* new_node = allocate_node(gap, Empty{});
insert(gap, new_node);
}
+ size_t unfilled_bytes = _total_gap_bytes - _total_gap_bytes_used;
log_trace(aot, alloc)("%zu bytes @ %p in a gap of %zu bytes (used gaps %zu times, remain gap = %zu bytes in %zu blocks)",
- num_bytes, result, gap_bytes, _total_gap_allocs, _total_gap_bytes, size());
+ num_bytes, result, gap_bytes, _total_gap_allocs, unfilled_bytes, size());
return result;
}
};
-size_t DumpRegion::_total_gap_bytes = 0;
-size_t DumpRegion::_total_gap_bytes_used = 0;
+size_t DumpRegion::_total_gap_bytes = 0; // All the gaps that have ever been created
+size_t DumpRegion::_total_gap_bytes_used = 0; // All the gaps that have been used
size_t DumpRegion::_total_gap_allocs = 0;
DumpRegion::AllocGapTree DumpRegion::_gap_tree;
@@ -418,20 +415,21 @@ void DumpRegion::report_gaps(DumpAllocStats* stats) {
});
double unfilled_percent = 0.0;
+ size_t unfilled_bytes = _total_gap_bytes - _total_gap_bytes_used;
if (_gap_tree.size() > 0) {
- unfilled_percent = percent_of(_total_gap_bytes, _total_gap_allocs);
+ unfilled_percent = percent_of(unfilled_bytes, _total_gap_bytes);
if (unfilled_percent > 5.0) {
// We have a limited number of small objects, so some small gaps may remain
// unfilled. If more than 5% of the gaps are unfilled, this likely indicates
// a systematic error that should be investigated. Otherwise, do not warn to
// avoid noise.
- log_warning(aot)("Unexpected %zu gaps (%zu bytes) for Klass alignment",
- _gap_tree.size(), _total_gap_bytes);
+ log_warning(aot)("Unexpected %zu gaps (%zu bytes, %.2f%%) for Klass alignment",
+ _gap_tree.size(), _total_gap_bytes, unfilled_percent);
}
}
if (_total_gap_allocs > 0) {
log_info(aot)("Allocated %zu objects of %zu bytes in gaps (remain = %zu bytes, %.2f%%)",
- _total_gap_allocs, _total_gap_bytes_used, _total_gap_bytes, unfilled_percent);
+ _total_gap_allocs, _total_gap_bytes_used, unfilled_bytes, unfilled_percent);
}
}
diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp
index d5ee16fec32..770c1e8fbc1 100644
--- a/src/hotspot/share/classfile/classFileParser.cpp
+++ b/src/hotspot/share/classfile/classFileParser.cpp
@@ -154,6 +154,8 @@
#define JAVA_27_VERSION 71
+#define JAVA_28_VERSION 72
+
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,
diff --git a/src/hotspot/share/classfile/dictionary.cpp b/src/hotspot/share/classfile/dictionary.cpp
index 0f79e7a5a69..a0cfe2a9893 100644
--- a/src/hotspot/share/classfile/dictionary.cpp
+++ b/src/hotspot/share/classfile/dictionary.cpp
@@ -31,7 +31,10 @@
#include "memory/metaspaceClosure.hpp"
#include "memory/resourceArea.hpp"
#include "oops/instanceKlass.hpp"
+#include "runtime/interfaceSupport.inline.hpp"
+#include "runtime/timerTrace.hpp"
#include "utilities/concurrentHashTable.inline.hpp"
+#include "utilities/concurrentHashTableTasks.inline.hpp"
#include "utilities/ostream.hpp"
#include "utilities/tableStatistics.hpp"
@@ -239,10 +242,24 @@ void Dictionary::verify() {
}
void Dictionary::print_table_statistics(outputStream* st, const char* table_name) {
- static TableStatistics ts;
+ TableStatistics stats;
auto sz = [&] (InstanceKlass** val) {
return sizeof(**val);
};
- ts = _table->statistics_get(Thread::current(), sz, ts);
- ts.print(st, table_name);
+ Thread* thread = Thread::current();
+ ConcurrentTable::StatisticsTask sts(_table);
+ if (!sts.prepare(thread)) {
+ st->print_cr("Failed to take statistics");
+ return;
+ }
+ TraceTime timer("GetStatistics", TRACETIME_LOG(Debug, perf));
+ while (sts.do_task(thread, sz)) {
+ sts.pause(thread);
+ if (thread->is_Java_thread()) {
+ ThreadBlockInVM tbivm(JavaThread::cast(thread));
+ }
+ sts.cont(thread);
+ }
+ stats = sts.done(thread);
+ stats.print(st, table_name);
}
diff --git a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp
index cec3586a50b..4a1b9ead116 100644
--- a/src/hotspot/share/classfile/vmIntrinsics.cpp
+++ b/src/hotspot/share/classfile/vmIntrinsics.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -527,6 +527,10 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
case vmIntrinsics::_intpoly_assign:
if (!UseIntPolyIntrinsics) return true;
break;
+ case vmIntrinsics::_intpoly_mult_25519:
+ case vmIntrinsics::_intpoly_square_25519:
+ if (!UseIntPoly25519Intrinsics) return true;
+ break;
case vmIntrinsics::_updateBytesCRC32C:
case vmIntrinsics::_updateDirectByteBufferCRC32C:
if (!UseCRC32CIntrinsics) return true;
diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp
index de4eea669a1..8833e4167f6 100644
--- a/src/hotspot/share/classfile/vmIntrinsics.hpp
+++ b/src/hotspot/share/classfile/vmIntrinsics.hpp
@@ -549,6 +549,13 @@ class methodHandle;
do_name(intPolyAssign_name, "conditionalAssign") \
do_signature(intPolyAssign_signature, "(I[J[J)V") \
\
+ /* support for sun.security.util.math.intpoly.IntegerPolynomial25519 */ \
+ do_class(sun_security_util_math_intpoly_IntegerPolynomial25519, "sun/security/util/math/intpoly/IntegerPolynomial25519") \
+ do_intrinsic(_intpoly_mult_25519, sun_security_util_math_intpoly_IntegerPolynomial25519, intPolyMult_name, intPolyMult_signature, F_R) \
+ do_intrinsic(_intpoly_square_25519, sun_security_util_math_intpoly_IntegerPolynomial25519, intPolySquare_name, intPolySquare_signature, F_R) \
+ do_name(intPolySquare_name, "square") \
+ do_signature(intPolySquare_signature, "([J[J)V") \
+ \
/* support for java.util.Base64.Encoder*/ \
do_class(java_util_Base64_Encoder, "java/util/Base64$Encoder") \
do_intrinsic(_base64_encodeBlock, java_util_Base64_Encoder, encodeBlock_name, encodeBlock_signature, F_R) \
diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp
index d0e549c9b11..8ea880c820f 100644
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp
@@ -729,7 +729,7 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) {
result = humongous_obj_allocate(word_size);
if (result != nullptr) {
policy()->old_gen_alloc_tracker()->
- add_allocated_humongous_bytes_since_last_gc(humongous_byte_size);
+ add_allocated_humongous_bytes(humongous_byte_size);
return result;
}
@@ -2861,7 +2861,7 @@ void G1CollectedHeap::record_obj_copy_mem_stats() {
G1ReservePercent);
policy()->old_gen_alloc_tracker()->
- add_allocated_bytes_since_last_gc(total_old_allocated * HeapWordSize);
+ add_allocated_non_humongous_bytes(total_old_allocated * HeapWordSize);
_gc_tracer_stw->report_evacuation_statistics(create_g1_evac_summary(&_survivor_evac_stats),
create_g1_evac_summary(&_old_evac_stats));
diff --git a/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp
new file mode 100644
index 00000000000..ab43a693a60
--- /dev/null
+++ b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "gc/g1/g1ConcurrentCycleTracker.hpp"
+#include "gc/g1/g1OldGenAllocationTracker.hpp"
+#include "utilities/checkedCast.hpp"
+#include "utilities/debug.hpp"
+
+void G1ConcurrentCycleTracker::reset() {
+ _state = CycleState::Inactive;
+ _total_pause_time_s = 0.0;
+ _cycle_start_time_s = 0.0;
+ _cycle_end_time_s = 0.0;
+
+ _humongous_bytes_at_start = 0;
+ _non_humongous_allocated_bytes = 0;
+ _peak_extra_humongous_occupancy_bytes = 0;
+}
+
+void G1ConcurrentCycleTracker::update_allocation_stats(G1AllocationIntervalStats interval_stats) {
+ if (!is_active()) {
+ return;
+ }
+
+ _non_humongous_allocated_bytes += interval_stats._non_humongous_allocated_bytes;
+
+ intptr_t delta_before = checked_cast(interval_stats._total_humongous_before_bytes) -
+ checked_cast(_humongous_bytes_at_start);
+
+ intptr_t delta_after = delta_before +
+ checked_cast(interval_stats._humongous_allocated_bytes);
+
+ if (delta_after > 0) {
+ _peak_extra_humongous_occupancy_bytes = MAX2(_peak_extra_humongous_occupancy_bytes, checked_cast(delta_after));
+ }
+}
+
+G1ConcurrentCycleTracker::G1ConcurrentCycleTracker()
+: _state(CycleState::Inactive),
+ _cycle_start_time_s(0.0),
+ _cycle_end_time_s(0.0),
+ _total_pause_time_s(0.0),
+ _humongous_bytes_at_start(0),
+ _non_humongous_allocated_bytes(0),
+ _peak_extra_humongous_occupancy_bytes(0)
+{ }
+
+void G1ConcurrentCycleTracker::record_cycle_start(double cycle_start_time_s, size_t humongous_bytes_after_pause) {
+ assert(_state == CycleState::Inactive, "Concurrent start out of order.");
+ _cycle_start_time_s = cycle_start_time_s;
+ _humongous_bytes_at_start = humongous_bytes_after_pause;
+ _state = CycleState::Active;
+}
+
+void G1ConcurrentCycleTracker::record_allocation_interval(Pause pause_type,
+ bool is_periodic_gc,
+ double pause_start_time_s,
+ double pause_end_time_s,
+ G1AllocationIntervalStats interval_stats) {
+ if (is_periodic_gc || pause_type == Pause::Full || pause_type == Pause::ConcurrentStartUndo) {
+ reset();
+ return;
+ }
+
+ if (pause_type == Pause::ConcurrentStartFull) {
+ record_cycle_start(pause_end_time_s, interval_stats._total_humongous_after_bytes);
+ return;
+ }
+
+ if (!is_active()) {
+ return;
+ }
+
+ update_allocation_stats(interval_stats);
+
+ if (pause_type == Pause::Mixed) {
+ complete_cycle(pause_start_time_s);
+ return;
+ }
+
+ assert(pause_type == Pause::Normal ||
+ pause_type == Pause::PrepareMixed ||
+ pause_type == Pause::Remark ||
+ pause_type == Pause::Cleanup,
+ "Unhandled pause type");
+
+ add_pause(pause_end_time_s - pause_start_time_s);
+}
+
+void G1ConcurrentCycleTracker::complete_cycle(double cycle_end_time_s) {
+ precond(is_active());
+
+ _cycle_end_time_s = cycle_end_time_s;
+ _state = CycleState::Complete;
+}
+
+G1ConcurrentCycleStats G1ConcurrentCycleTracker::get_and_reset_cycle_stats() {
+ precond(has_completed_cycle());
+
+ double cycle_duration = (_cycle_end_time_s - _cycle_start_time_s - _total_pause_time_s);
+
+ G1ConcurrentCycleStats stats{
+ cycle_duration,
+ _non_humongous_allocated_bytes,
+ _peak_extra_humongous_occupancy_bytes
+ };
+
+ reset();
+ return stats;
+}
diff --git a/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp
new file mode 100644
index 00000000000..7d575e7abcd
--- /dev/null
+++ b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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_G1_G1CONCURRENTCYCLETRACKER_HPP
+#define SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP
+
+#include "gc/g1/g1CollectorState.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+struct G1AllocationIntervalStats;
+
+// The sampling interval for G1ConcurrentCycleTracker covers the concurrent cycle
+// from the end of the Concurrent Start GC to start of the first Mixed GC.
+struct G1ConcurrentCycleStats {
+ double _cycle_duration_s;
+ size_t _non_humongous_allocated_bytes;
+ size_t _peak_extra_humongous_occupancy_bytes;
+
+ G1ConcurrentCycleStats(double cycle_duration_s,
+ size_t non_humongous_allocated_bytes,
+ size_t peak_extra_humongous_occupancy_bytes)
+ : _cycle_duration_s(cycle_duration_s),
+ _non_humongous_allocated_bytes(non_humongous_allocated_bytes),
+ _peak_extra_humongous_occupancy_bytes(peak_extra_humongous_occupancy_bytes)
+ { }
+};
+
+class G1ConcurrentCycleTracker {
+ using Pause = G1CollectorState::Pause;
+
+ enum class CycleState {
+ Inactive,
+ Active,
+ Complete,
+ };
+
+ CycleState _state;
+ double _cycle_start_time_s;
+ double _cycle_end_time_s;
+ double _total_pause_time_s;
+
+ // allocation accounting
+ size_t _humongous_bytes_at_start;
+ size_t _non_humongous_allocated_bytes;
+ size_t _peak_extra_humongous_occupancy_bytes;
+
+ void reset();
+
+ bool is_active() const {
+ return _state == CycleState::Active;
+ }
+ void update_allocation_stats(G1AllocationIntervalStats interval_stats);
+
+ void add_pause(double pause_duration_s) {
+ _total_pause_time_s += pause_duration_s;
+ }
+
+ void record_cycle_start(double cycle_start_time_s, size_t humongous_bytes_after_pause);
+
+ void complete_cycle(double cycle_end_time_s);
+
+ public:
+ G1ConcurrentCycleTracker();
+
+ void record_allocation_interval(Pause pause_type,
+ bool is_periodic_gc,
+ double pause_start_time_s,
+ double pause_end_time_s,
+ G1AllocationIntervalStats interval_stats);
+
+ void abort_cycle() {
+ reset();
+ }
+
+ bool has_completed_cycle() const {
+ return _state == CycleState::Complete;
+ }
+
+ size_t non_humongous_allocated_bytes() const {
+ return _non_humongous_allocated_bytes;
+ }
+
+ size_t peak_extra_humongous_occupancy_bytes() const {
+ return _peak_extra_humongous_occupancy_bytes;
+ }
+
+ G1ConcurrentCycleStats get_and_reset_cycle_stats();
+};
+
+#endif // SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP
diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp
index 83dda2a043b..4afc7fa8ff1 100644
--- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp
+++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp
@@ -714,6 +714,11 @@ private:
}
HeapWord* region_clear_limit(G1HeapRegion* r) {
+ // A garbage collection might have made the region unavailable after a yield during
+ // clearing. Just return bottom as the limit, causing the clearing for this region to end.
+ if (G1CollectedHeap::heap()->region_at_or_null(r->hrm_index()) == nullptr) {
+ return r->bottom();
+ }
// During a Concurrent Undo Mark cycle, the per region top_at_mark_start and
// live_words data are current wrt to the _mark_bitmap. We use this information
// to only clear ranges of the bitmap that require clearing.
@@ -743,7 +748,7 @@ private:
}
HeapWord* cur = r->bottom();
- HeapWord* const end = region_clear_limit(r);
+ HeapWord* end = region_clear_limit(r);
size_t const chunk_size_in_words = G1ClearBitMapTask::chunk_size() / HeapWordSize;
@@ -761,8 +766,12 @@ private:
assert(!suspendible() || _cm->is_in_reset_for_next_cycle(), "invariant");
// Abort iteration if necessary.
- if (has_aborted()) {
- return true;
+ if (suspendible() && _cm->do_yield_check()) {
+ if (_cm->has_aborted()) {
+ return true;
+ }
+ // Re-read end. The region might have been uncommitted.
+ end = region_clear_limit(r);
}
}
assert(cur >= end, "Must have completed iteration over the bitmap for region %u.", r->hrm_index());
diff --git a/src/hotspot/share/gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp b/src/hotspot/share/gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp
deleted file mode 100644
index f8bad4bdcd7..00000000000
--- a/src/hotspot/share/gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please 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_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP
-#define SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP
-
-#include "utilities/debug.hpp"
-#include "utilities/globalDefinitions.hpp"
-
-// Used to track time from the end of concurrent start to the first mixed GC.
-// After calling the concurrent start/mixed gc notifications, the result can be
-// obtained in get_and_reset_last_marking_time() once, after which the tracking resets.
-// Any pauses recorded by add_pause() will be subtracted from that results.
-class G1ConcurrentStartToMixedTimeTracker {
-private:
- bool _active;
- double _concurrent_start_end_time;
- double _mixed_start_time;
- double _total_pause_time;
-
- double wall_time() const {
- return _mixed_start_time - _concurrent_start_end_time;
- }
-public:
- G1ConcurrentStartToMixedTimeTracker() { reset(); }
-
- // Record concurrent start pause end, starting the time tracking.
- void record_concurrent_start_end(double end_time) {
- assert(!_active, "Concurrent start out of order.");
- _concurrent_start_end_time = end_time;
- _active = true;
- }
-
- // Record the first mixed gc pause start, ending the time tracking.
- void record_mixed_gc_start(double start_time) {
- if (_active) {
- _mixed_start_time = start_time;
- _active = false;
- }
- }
-
- double get_and_reset_last_marking_time() {
- assert(has_result(), "Do not have all measurements yet.");
- double result = (_mixed_start_time - _concurrent_start_end_time) - _total_pause_time;
- reset();
- return result;
- }
-
- void reset() {
- _active = false;
- _total_pause_time = 0.0;
- _concurrent_start_end_time = -1.0;
- _mixed_start_time = -1.0;
- }
-
- void add_pause(double time) {
- if (_active) {
- _total_pause_time += time;
- }
- }
-
- bool is_active() const { return _active; }
-
- // Returns whether we have a result that can be retrieved.
- bool has_result() const { return _mixed_start_time > 0.0 && _concurrent_start_end_time > 0.0; }
-};
-
-#endif // SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP
diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.cpp b/src/hotspot/share/gc/g1/g1IHOPControl.cpp
index 164486123f7..782cdadc13b 100644
--- a/src/hotspot/share/gc/g1/g1IHOPControl.cpp
+++ b/src/hotspot/share/gc/g1/g1IHOPControl.cpp
@@ -31,19 +31,14 @@
double G1IHOPControl::predict(const TruncatedSeq* seq) const {
assert(_is_adaptive, "precondition");
assert(_predictor != nullptr, "precondition");
-
- return _predictor->predict_zero_bounded(seq);
+ return _predictor->predict_zero_bounded(seq);
}
bool G1IHOPControl::have_enough_data_for_prediction() const {
assert(_is_adaptive, "precondition");
return ((size_t)_marking_start_to_mixed_time_s.num() >= G1AdaptiveIHOPNumInitialSamples) &&
- ((size_t)_old_gen_alloc_rate.num() >= G1AdaptiveIHOPNumInitialSamples);
-}
-
-double G1IHOPControl::last_marking_start_to_mixed_time_s() const {
- return _marking_start_to_mixed_time_s.last();
+ ((size_t)_old_non_humongous_alloc_rate.num() >= G1AdaptiveIHOPNumInitialSamples);
}
size_t G1IHOPControl::effective_target_occupancy() const {
@@ -66,7 +61,6 @@ size_t G1IHOPControl::effective_target_occupancy() const {
}
G1IHOPControl::G1IHOPControl(double ihop_percent,
- const G1OldGenAllocationTracker* old_gen_alloc_tracker,
bool adaptive,
const G1Predictions* predictor,
size_t heap_reserve_percent,
@@ -76,11 +70,10 @@ G1IHOPControl::G1IHOPControl(double ihop_percent,
_target_occupancy(0),
_heap_reserve_percent(heap_reserve_percent),
_heap_waste_percent(heap_waste_percent),
- _last_allocation_time_s(0.0),
- _old_gen_alloc_tracker(old_gen_alloc_tracker),
_predictor(predictor),
_marking_start_to_mixed_time_s(10, 0.05),
- _old_gen_alloc_rate(10, 0.05),
+ _old_non_humongous_alloc_rate(10, 0.05),
+ _peak_extra_humongous_occupancy_in_mark_cycle(10, 0.05),
_expected_young_gen_at_first_mixed_gc(0) {
assert(_initial_ihop_percent >= 0.0 && _initial_ihop_percent <= 100.0,
"IHOP percent out of range: %.3f", ihop_percent);
@@ -93,22 +86,28 @@ void G1IHOPControl::update_target_occupancy(size_t new_target_occupancy) {
_target_occupancy = new_target_occupancy;
}
-void G1IHOPControl::report_statistics(G1NewTracer* new_tracer, size_t non_young_occupancy) {
- print_log(non_young_occupancy);
- send_trace_event(new_tracer, non_young_occupancy);
+void G1IHOPControl::report_statistics(G1NewTracer* new_tracer,
+ size_t non_young_occupancy,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy) {
+ print_log(non_young_occupancy, non_humongous_allocation, peak_extra_humongous_occupancy);
+ send_trace_event(new_tracer, non_young_occupancy,
+ non_humongous_allocation, peak_extra_humongous_occupancy);
}
-void G1IHOPControl::update_allocation_info(double allocation_time_s, size_t expected_young_gen_size) {
- assert(allocation_time_s > 0, "Invalid allocation time: %.3f", allocation_time_s);
- _last_allocation_time_s = allocation_time_s;
- double alloc_rate = _old_gen_alloc_tracker->last_period_old_gen_growth() / allocation_time_s;
- _old_gen_alloc_rate.add(alloc_rate);
+void G1IHOPControl::record_expected_young_gen_size(size_t expected_young_gen_size) {
_expected_young_gen_at_first_mixed_gc = expected_young_gen_size;
}
-void G1IHOPControl::add_marking_start_to_mixed_length(double length_s) {
- assert(length_s >= 0.0, "Invalid marking length: %.3f", length_s);
- _marking_start_to_mixed_time_s.add(length_s);
+void G1IHOPControl::record_concurrent_cycle(double marking_start_to_mixed_time_s,
+ size_t non_humongous_bytes,
+ size_t peak_extra_humongous_occupancy_bytes) {
+ assert(marking_start_to_mixed_time_s > 0.0, "Invalid concurrent cycle duration: %.3f", marking_start_to_mixed_time_s);
+
+ double non_humongous_rate = non_humongous_bytes / marking_start_to_mixed_time_s;
+ _marking_start_to_mixed_time_s.add(marking_start_to_mixed_time_s);
+ _old_non_humongous_alloc_rate.add(non_humongous_rate);
+ _peak_extra_humongous_occupancy_in_mark_cycle.add(peak_extra_humongous_occupancy_bytes);
}
// Determine the old generation occupancy threshold at which to start
@@ -121,80 +120,93 @@ size_t G1IHOPControl::old_gen_threshold_for_conc_mark_start() const {
return (size_t)(_initial_ihop_percent * _target_occupancy / 100.0);
}
- // During the time between marking start and the first Mixed GC,
- // additional memory will be consumed:
- // - Old gen grows due to allocations:
- // old_gen_alloc_bytes = old_gen_alloc_rate * marking_start_to_mixed_time
- // - Young gen will occupy a certain size at the first Mixed GC:
- // expected_young_gen_at_first_mixed_gc
- double marking_start_to_mixed_time = predict(&_marking_start_to_mixed_time_s);
- double old_gen_alloc_rate = predict(&_old_gen_alloc_rate);
- size_t old_gen_alloc_bytes = (size_t)(marking_start_to_mixed_time * old_gen_alloc_rate);
-
- // Therefore, the total heap occupancy at the first Mixed GC is:
- // current_old_gen + old_gen_growth + expected_young_gen_at_first_mixed_gc
+ // Between Concurrent Start GC and the first Mixed GC (i.e. concurrent cycle),
+ // we expect extra heap occupancy from three sources:
+ // - non-humongous allocations into the old-gen
+ // - peak extra humongous occupancy during the cycle, relative to the humongous occupancy
+ // at the end of the Concurrent Start GC.
+ // - we also wish to maintain the current desired young generation until the first Mixed-gc;
+ // promotions into the old gen should not shrink the young gen and degrade performance.
//
- // To ensure this does not exceed the target_heap_occupancy, we work
- // backwards to compute the old gen occupancy at which marking must start:
- // mark_start_threshold = target_heap_occupancy -
- // (old_gen_growth + expected_young_gen_at_first_mixed_gc)
+ // We therefore start marking early enough such that:
+ //
+ // old_gen_at_concurrent_start +
+ // predicted_non_hum_old_growth +
+ // predicted_peak_extra_humongous_occupancy +
+ // expected_young_gen_at_first_mixed_gc
+ //
+ // stays below the effective target occupancy.
+ double marking_start_to_mixed_time = predict(&_marking_start_to_mixed_time_s);
+ double old_non_humongous_alloc_rate = predict(&_old_non_humongous_alloc_rate);
+ size_t old_non_humongous_alloc_bytes = (size_t)(marking_start_to_mixed_time * old_non_humongous_alloc_rate);
- size_t predicted_needed = old_gen_alloc_bytes + _expected_young_gen_at_first_mixed_gc;
+ size_t predicted_peak_extra_humongous_occupancy =
+ predict(&_peak_extra_humongous_occupancy_in_mark_cycle);
+
+ size_t reserve_for_young_regions = _expected_young_gen_at_first_mixed_gc;
size_t target_heap_occupancy = effective_target_occupancy();
- return predicted_needed < target_heap_occupancy
- ? target_heap_occupancy - predicted_needed
- : 0;
+ size_t needed_for_concurrent_cycle = reserve_for_young_regions +
+ old_non_humongous_alloc_bytes +
+ predicted_peak_extra_humongous_occupancy;
+
+ size_t threshold = needed_for_concurrent_cycle < target_heap_occupancy ?
+ target_heap_occupancy - needed_for_concurrent_cycle : 0;
+ return threshold;
}
-void G1IHOPControl::print_log(size_t non_young_occupancy) {
+void G1IHOPControl::print_log(size_t non_young_occupancy,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy) {
assert(_target_occupancy > 0, "Target occupancy still not updated yet.");
size_t old_gen_mark_start_threshold = old_gen_threshold_for_conc_mark_start();
- log_debug(gc, ihop)("Basic information (value update), old-gen threshold: %zuB (%1.2f%%), target occupancy: %zuB, old-gen occupancy: %zuB (%1.2f%%), "
- "recent old-gen allocation size: %zuB, recent allocation duration: %1.2fms, recent old-gen allocation rate: %1.2fB/s, recent marking phase length: %1.2fms",
+ log_debug(gc, ihop)("Basic information (value update), old-gen threshold: %zuB (%1.2f%%), target occupancy: %zuB, old-gen occupancy: %zuB (%1.2f%%)",
old_gen_mark_start_threshold,
percent_of(old_gen_mark_start_threshold, _target_occupancy),
_target_occupancy,
non_young_occupancy,
- percent_of(non_young_occupancy, _target_occupancy),
- _old_gen_alloc_tracker->last_period_old_gen_bytes(),
- _last_allocation_time_s * 1000.0,
- _last_allocation_time_s > 0.0 ? _old_gen_alloc_tracker->last_period_old_gen_bytes() / _last_allocation_time_s : 0.0,
- last_marking_start_to_mixed_time_s() * 1000.0);
+ percent_of(non_young_occupancy, _target_occupancy));
- if (!_is_adaptive) {
+ if (!_is_adaptive || !have_enough_data_for_prediction()) {
return;
}
size_t effective_target = effective_target_occupancy();
- log_debug(gc, ihop)("Adaptive IHOP information (value update), prediction active: %s, old-gen threshold: %zuB (%1.2f%%), internal target occupancy: %zuB, "
- "old-gen occupancy: %zuB, additional buffer size: %zuB, predicted old-gen allocation rate: %1.2fB/s, "
- "predicted marking phase length: %1.2fms",
- BOOL_TO_STR(have_enough_data_for_prediction()),
+ log_debug(gc, ihop)("Adaptive IHOP information (value update), old-gen threshold: %zuB (%1.2f%%), internal target occupancy: %zuB, "
+ "old-gen occupancy: %zuB (%1.2f%%), additional buffer size: %zuB, "
+ "current non-humongous allocation: %zuB, current peak extra humongous occupancy: %zuB, "
+ "predicted old-gen non-humongous allocation rate: %1.2fB/s, predicted peak extra humongous occupancy: %1.2fB, "
+ "predicted concurrent cycle duration: %1.2fms",
old_gen_mark_start_threshold,
percent_of(old_gen_mark_start_threshold, effective_target),
effective_target,
non_young_occupancy,
+ percent_of(non_young_occupancy, effective_target),
_expected_young_gen_at_first_mixed_gc,
- predict(&_old_gen_alloc_rate),
+ non_humongous_allocation, peak_extra_humongous_occupancy,
+ predict(&_old_non_humongous_alloc_rate),
+ predict(&_peak_extra_humongous_occupancy_in_mark_cycle),
predict(&_marking_start_to_mixed_time_s) * 1000.0);
}
-void G1IHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) {
+void G1IHOPControl::send_trace_event(G1NewTracer* tracer,
+ size_t non_young_occupancy,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy) {
assert(_target_occupancy > 0, "Target occupancy still not updated yet.");
tracer->report_basic_ihop_statistics(old_gen_threshold_for_conc_mark_start(),
_target_occupancy,
- non_young_occupancy,
- _old_gen_alloc_tracker->last_period_old_gen_bytes(),
- _last_allocation_time_s,
- last_marking_start_to_mixed_time_s());
+ non_young_occupancy);
if (_is_adaptive) {
tracer->report_adaptive_ihop_statistics(old_gen_threshold_for_conc_mark_start(),
effective_target_occupancy(),
non_young_occupancy,
_expected_young_gen_at_first_mixed_gc,
- predict(&_old_gen_alloc_rate),
+ non_humongous_allocation,
+ peak_extra_humongous_occupancy,
+ predict(&_old_non_humongous_alloc_rate),
+ predict(&_peak_extra_humongous_occupancy_in_mark_cycle),
predict(&_marking_start_to_mixed_time_s),
have_enough_data_for_prediction());
}
diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.hpp b/src/hotspot/share/gc/g1/g1IHOPControl.hpp
index 2836408978b..df92f31065f 100644
--- a/src/hotspot/share/gc/g1/g1IHOPControl.hpp
+++ b/src/hotspot/share/gc/g1/g1IHOPControl.hpp
@@ -25,7 +25,6 @@
#ifndef SHARE_GC_G1_G1IHOPCONTROL_HPP
#define SHARE_GC_G1_G1IHOPCONTROL_HPP
-#include "gc/g1/g1OldGenAllocationTracker.hpp"
#include "memory/allocation.hpp"
#include "utilities/numberSeq.hpp"
@@ -53,16 +52,15 @@ class G1IHOPControl : public CHeapObj {
// Percentage of free heap that should be considered as waste.
const size_t _heap_waste_percent;
- // Most recent complete mutator allocation period in seconds.
- double _last_allocation_time_s;
- const G1OldGenAllocationTracker* _old_gen_alloc_tracker;
-
const G1Predictions* _predictor;
// Wall-clock time in seconds from marking start to the first mixed GC,
// excluding GC Pause time.
TruncatedSeq _marking_start_to_mixed_time_s;
- // Old generation allocation rate in bytes per second.
- TruncatedSeq _old_gen_alloc_rate;
+ // Track old-generation allocations during a concurrent cycle: end of the
+ // Concurrent Start to the first Mixed GC.
+ // These values are used only when G1UseAdaptiveIHOP is enabled.
+ TruncatedSeq _old_non_humongous_alloc_rate;
+ TruncatedSeq _peak_extra_humongous_occupancy_in_mark_cycle;
// The most recent unrestrained size of the young gen. This is used as an additional
// factor in the calculation of the threshold, as the threshold is based on
@@ -77,19 +75,23 @@ class G1IHOPControl : public CHeapObj {
double predict(const TruncatedSeq* seq) const;
bool have_enough_data_for_prediction() const;
- double last_marking_start_to_mixed_time_s() const;
// The "effective" target occupancy the algorithm wants to keep until the start
// of Mixed GCs. This is typically lower than the target occupancy, as the
// algorithm needs to consider restrictions by the environment.
size_t effective_target_occupancy() const;
- void print_log(size_t non_young_occupancy);
- void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy);
+ void print_log(size_t non_young_occupancy,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy);
+
+ void send_trace_event(G1NewTracer* tracer,
+ size_t non_young_occupancy,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy);
public:
G1IHOPControl(double ihop_percent,
- const G1OldGenAllocationTracker* old_gen_alloc_tracker,
bool adaptive,
const G1Predictions* predictor,
size_t heap_reserve_percent,
@@ -98,26 +100,23 @@ class G1IHOPControl : public CHeapObj {
// Adjust target occupancy.
void update_target_occupancy(size_t new_target_occupancy);
- void update_target_after_marking_phase();
-
- // Update allocation rate information and current expected young gen size for the
- // first mixed gc needed for the predictor. Allocation rate is given as the
- // separately passed in allocation increment and the time passed (mutator time)
- // for the latest allocation increment here. Allocation size is the memory needed
- // during the mutator before and the first mixed gc pause itself.
+ // Updates expected young gen size for the first mixed gc needed for the predictor.
// Contents include young gen at that point, and the memory required for evacuating
// the collection set in that first mixed gc (including waste caused by PLAB
// allocation etc.).
- void update_allocation_info(double allocation_time_s, size_t expected_young_gen_size);
+ void record_expected_young_gen_size(size_t expected_young_gen_size);
- // Update the time spent in the mutator beginning from the end of concurrent start to
- // the first mixed gc.
- void add_marking_start_to_mixed_length(double length_s);
+ void record_concurrent_cycle(double marking_start_to_mixed_time_s,
+ size_t non_humongous_bytes,
+ size_t peak_extra_humongous_occupancy);
// Get the current non-young occupancy at which concurrent marking should start.
size_t old_gen_threshold_for_conc_mark_start() const;
- void report_statistics(G1NewTracer* tracer, size_t non_young_occupancy);
+ void report_statistics(G1NewTracer* tracer,
+ size_t non_young_occupancy,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy);
};
#endif // SHARE_GC_G1_G1IHOPCONTROL_HPP
diff --git a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp
index ec3d39d7e50..7355a40c348 100644
--- a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp
+++ b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp
@@ -26,36 +26,43 @@
#include "logging/log.hpp"
G1OldGenAllocationTracker::G1OldGenAllocationTracker() :
- _last_period_old_gen_bytes(0),
- _last_period_old_gen_growth(0),
- _humongous_bytes_after_last_gc(0),
- _allocated_bytes_since_last_gc(0),
- _allocated_humongous_bytes_since_last_gc(0) {
+ _humongous_bytes_after_last_pause(0),
+ _allocated_non_humongous_bytes_since_last_pause(0),
+ _allocated_humongous_bytes_since_last_pause(0) {
}
-void G1OldGenAllocationTracker::reset_after_gc(size_t humongous_bytes_after_gc) {
+G1AllocationIntervalStats G1OldGenAllocationTracker::end_allocation_interval(size_t humongous_bytes_after_pause) {
// Calculate actual increase in old, taking eager reclaim into consideration.
- size_t last_period_humongous_increase = 0;
- if (humongous_bytes_after_gc > _humongous_bytes_after_last_gc) {
- last_period_humongous_increase = humongous_bytes_after_gc - _humongous_bytes_after_last_gc;
- assert(last_period_humongous_increase <= _allocated_humongous_bytes_since_last_gc,
+ size_t last_interval_humongous_increase = 0;
+ if (humongous_bytes_after_pause > _humongous_bytes_after_last_pause) {
+ last_interval_humongous_increase = humongous_bytes_after_pause - _humongous_bytes_after_last_pause;
+ assert(last_interval_humongous_increase <= _allocated_humongous_bytes_since_last_pause,
"Increase larger than allocated %zu <= %zu",
- last_period_humongous_increase, _allocated_humongous_bytes_since_last_gc);
+ last_interval_humongous_increase, _allocated_humongous_bytes_since_last_pause);
}
- _last_period_old_gen_growth = _allocated_bytes_since_last_gc + last_period_humongous_increase;
- // Calculate and record needed values.
- _last_period_old_gen_bytes = _allocated_bytes_since_last_gc + _allocated_humongous_bytes_since_last_gc;
- _humongous_bytes_after_last_gc = humongous_bytes_after_gc;
+ size_t last_interval_old_gen_growth = _allocated_non_humongous_bytes_since_last_pause +
+ last_interval_humongous_increase;
- log_debug(gc, alloc, stats)("Old generation allocation in the last mutator period, "
+ G1AllocationIntervalStats allocation_interval_stats{
+ _allocated_non_humongous_bytes_since_last_pause,
+ _allocated_humongous_bytes_since_last_pause,
+ _humongous_bytes_after_last_pause,
+ humongous_bytes_after_pause
+ };
+
+ _humongous_bytes_after_last_pause = humongous_bytes_after_pause;
+
+ log_debug(gc, alloc, stats)("Old generation allocation in the last allocation interval, "
"old gen allocated: %zuB, humongous allocated: %zuB, "
"old gen growth: %zuB.",
- _allocated_bytes_since_last_gc,
- _allocated_humongous_bytes_since_last_gc,
- _last_period_old_gen_growth);
+ _allocated_non_humongous_bytes_since_last_pause,
+ _allocated_humongous_bytes_since_last_pause,
+ last_interval_old_gen_growth);
- // Reset for next mutator period.
- _allocated_bytes_since_last_gc = 0;
- _allocated_humongous_bytes_since_last_gc = 0;
+ // Reset for the next interval.
+ _allocated_non_humongous_bytes_since_last_pause = 0;
+ _allocated_humongous_bytes_since_last_pause = 0;
+
+ return allocation_interval_stats;
}
diff --git a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp
index aa5e3c6c942..218f65cf7c1 100644
--- a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp
+++ b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp
@@ -22,46 +22,71 @@
*
*/
-#ifndef SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
-#define SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
+#ifndef SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
+#define SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
-#include "gc/g1/g1HeapRegion.hpp"
#include "memory/allocation.hpp"
+// Allocation statistics for an allocation interval, i.e. the interval between two
+// consecutive STW pauses.
+//
+// The allocation counters record allocations made during that interval.
+//
+// _total_humongous_before_bytes is the humongous occupancy after the previous
+// pause (at the start of the allocation interval).
+//
+// _total_humongous_after_bytes is the humongous occupancy after the current
+// pause (at the end of the allocation interval).
+struct G1AllocationIntervalStats {
+ size_t _non_humongous_allocated_bytes;
+ size_t _humongous_allocated_bytes;
+ size_t _total_humongous_before_bytes;
+ size_t _total_humongous_after_bytes;
+
+ G1AllocationIntervalStats(size_t non_humongous_allocated_bytes,
+ size_t humongous_allocated_bytes,
+ size_t total_humongous_before_bytes,
+ size_t total_humongous_after_bytes)
+ : _non_humongous_allocated_bytes(non_humongous_allocated_bytes),
+ _humongous_allocated_bytes(humongous_allocated_bytes),
+ _total_humongous_before_bytes(total_humongous_before_bytes),
+ _total_humongous_after_bytes(total_humongous_after_bytes)
+ { }
+
+ void record_humongous_allocation(size_t humongous_allocation_bytes) {
+ _humongous_allocated_bytes += humongous_allocation_bytes;
+ _total_humongous_after_bytes += humongous_allocation_bytes;
+ }
+};
+
// Track allocation details in the old generation.
class G1OldGenAllocationTracker : public CHeapObj {
- // Total number of bytes allocated in the old generation at the end
- // of the last gc.
- size_t _last_period_old_gen_bytes;
- // Total growth of the old geneneration since the last gc,
- // taking eager-reclaim into consideration.
- size_t _last_period_old_gen_growth;
+ // Total size of humongous objects after the last STW pause.
+ size_t _humongous_bytes_after_last_pause;
- // Total size of humongous objects for last gc.
- size_t _humongous_bytes_after_last_gc;
-
- // Non-humongous old generation allocations since the last gc.
- size_t _allocated_bytes_since_last_gc;
- // Humongous allocations during last mutator period.
- size_t _allocated_humongous_bytes_since_last_gc;
+ // Non-humongous old generation allocations since the last STW pause.
+ size_t _allocated_non_humongous_bytes_since_last_pause;
+ // Humongous allocations during last allocation interval.
+ size_t _allocated_humongous_bytes_since_last_pause;
public:
G1OldGenAllocationTracker();
- void add_allocated_bytes_since_last_gc(size_t bytes) { _allocated_bytes_since_last_gc += bytes; }
- void add_allocated_humongous_bytes_since_last_gc(size_t bytes) { _allocated_humongous_bytes_since_last_gc += bytes; }
-
- // Record a humongous allocation in a collection pause. This allocation
- // is accounted to the previous mutator period.
- void record_collection_pause_humongous_allocation(size_t bytes) {
- _humongous_bytes_after_last_gc += bytes;
+ void add_allocated_non_humongous_bytes(size_t bytes) {
+ _allocated_non_humongous_bytes_since_last_pause += bytes;
+ }
+ void add_allocated_humongous_bytes(size_t bytes) {
+ _allocated_humongous_bytes_since_last_pause += bytes;
}
- size_t last_period_old_gen_bytes() const { return _last_period_old_gen_bytes; }
- size_t last_period_old_gen_growth() const { return _last_period_old_gen_growth; };
+ // Record a humongous allocation in a collection pause. This allocation
+ // is accounted to the previous allocation interval.
+ void record_collection_pause_humongous_allocation(size_t bytes) {
+ _humongous_bytes_after_last_pause += bytes;
+ }
- // Calculates and resets stats after a collection.
- void reset_after_gc(size_t humongous_bytes_after_gc);
+ // Calculate and reset allocation statistics after a pause.
+ G1AllocationIntervalStats end_allocation_interval(size_t humongous_bytes_after_pause);
};
-#endif // SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
+#endif // SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp
index f9e084248b9..35211938065 100644
--- a/src/hotspot/share/gc/g1/g1Policy.cpp
+++ b/src/hotspot/share/gc/g1/g1Policy.cpp
@@ -55,8 +55,9 @@ G1Policy::G1Policy(STWGCTimer* gc_timer) :
_analytics(new G1Analytics(&_predictor)),
_remset_tracker(),
_mmu_tracker(new G1MMUTracker(GCPauseIntervalMillis / 1000.0, MaxGCPauseMillis / 1000.0)),
+ _concurrent_cycle_tracker(),
_old_gen_alloc_tracker(),
- _ihop_control(create_ihop_control(&_old_gen_alloc_tracker, &_predictor)),
+ _ihop_control(create_ihop_control(&_predictor)),
_policy_counters(new GCPolicyCounters("GarbageFirst", 1, 2)),
_cur_pause_start_sec(0.0),
_young_list_desired_length(0),
@@ -68,7 +69,6 @@ G1Policy::G1Policy(STWGCTimer* gc_timer) :
_young_gen_sizer(),
_free_regions_at_end_of_collection(0),
_pending_cards_from_gc(0),
- _concurrent_start_to_mixed(),
_collection_set(nullptr),
_g1h(nullptr),
_phase_times_timer(gc_timer),
@@ -160,7 +160,7 @@ void G1Policy::record_new_heap_size(uint new_number_of_regions) {
double reserve_regions_d = (double) new_number_of_regions * _reserve_factor;
// We use ceiling so that if reserve_regions_d is > 0.0 (but
// smaller than 1.0) we'll get 1.
- _reserve_regions = (uint) ceil(reserve_regions_d);
+ _reserve_regions.store_relaxed((uint) ceil(reserve_regions_d));
_young_gen_sizer.heap_size_changed(new_number_of_regions);
@@ -186,8 +186,22 @@ void G1Policy::update_young_length_bounds() {
void G1Policy::update_young_length_bounds(size_t pending_cards, size_t card_rs_length, size_t code_root_rs_length) {
uint old_young_list_target_length = young_list_target_length();
- uint new_young_list_desired_length = calculate_young_desired_length(pending_cards, card_rs_length, code_root_rs_length);
- uint new_young_list_target_length = calculate_young_target_length(new_young_list_desired_length);
+ uint min_young_length_by_sizer = _young_gen_sizer.min_desired_young_length();
+ uint max_young_length_by_sizer = _young_gen_sizer.max_desired_young_length();
+
+ if (max_young_length_by_sizer < min_young_length_by_sizer) {
+ // This can happen due to races with heap_size_changed() at mutator time. Do not update the young gen
+ // lengths. Will be updated on the next regular call anyway.
+ assert(!SafepointSynchronize::is_at_safepoint(), "must be");
+ return;
+ }
+
+ uint new_young_list_desired_length = calculate_young_desired_length(pending_cards,
+ card_rs_length,
+ code_root_rs_length,
+ min_young_length_by_sizer,
+ max_young_length_by_sizer);
+ uint new_young_list_target_length = calculate_young_target_length(new_young_list_desired_length, min_young_length_by_sizer);
log_trace(gc, ergo, heap)("Young list length update: pending cards %zu card_rs_length %zu old target %u desired: %u target: %u",
pending_cards,
@@ -224,9 +238,9 @@ void G1Policy::update_young_length_bounds(size_t pending_cards, size_t card_rs_l
//
uint G1Policy::calculate_young_desired_length(size_t pending_cards,
size_t card_rs_length,
- size_t code_root_rs_length) const {
- uint min_young_length_by_sizer = _young_gen_sizer.min_desired_young_length();
- uint max_young_length_by_sizer = _young_gen_sizer.max_desired_young_length();
+ size_t code_root_rs_length,
+ uint min_young_length_by_sizer,
+ uint max_young_length_by_sizer) const {
assert(min_young_length_by_sizer >= 1, "invariant");
assert(max_young_length_by_sizer >= min_young_length_by_sizer, "invariant");
@@ -302,7 +316,7 @@ uint G1Policy::calculate_young_desired_length(size_t pending_cards,
// Limit the desired (wished) young length by current free regions. If the request
// can be satisfied without using up reserve regions, do so, otherwise eat into
// the reserve, giving away at most what the heap sizer allows.
-uint G1Policy::calculate_young_target_length(uint desired_young_length) const {
+uint G1Policy::calculate_young_target_length(uint desired_young_length, uint min_young_length_by_sizer) const {
uint allocated_young_length = _g1h->young_regions_count();
uint receiving_additional_eden;
@@ -319,8 +333,14 @@ uint G1Policy::calculate_young_target_length(uint desired_young_length) const {
// do, we at most eat the sizer's minimum regions into the reserve or half the
// reserve rounded up (if possible; this is an arbitrary value).
- uint max_to_eat_into_reserve = MIN2(_young_gen_sizer.min_desired_young_length(),
- (_reserve_regions + 1) / 2);
+ // The heap reserve needs to be snapshotted for consistent use in the following.
+ // It can be concurrently modified by the mutator as it expands the heap. It can
+ // only increase at that time, so this is a conservative snapshot. So at worst this
+ // method will return a too small young gen length in that case.
+ uint reserve_regions = _reserve_regions.load_relaxed();
+
+ uint max_to_eat_into_reserve = MIN2(min_young_length_by_sizer,
+ (reserve_regions + 1) / 2);
log_trace(gc, ergo, heap)("Young target length: Common "
"free regions at end of collection %u "
@@ -329,14 +349,14 @@ uint G1Policy::calculate_young_target_length(uint desired_young_length) const {
"max to eat into reserve %u",
_free_regions_at_end_of_collection,
desired_young_length,
- _reserve_regions,
+ reserve_regions,
max_to_eat_into_reserve);
uint survivor_regions_count = _g1h->survivor_regions_count();
uint desired_eden_length = desired_young_length - survivor_regions_count;
uint allocated_eden_length = allocated_young_length - survivor_regions_count;
- if (_free_regions_at_end_of_collection <= _reserve_regions) {
+ if (_free_regions_at_end_of_collection <= reserve_regions) {
// Fully eat (or already eating) into the reserve, hand back at most absolute_min_length regions.
uint receiving_eden = MIN3(_free_regions_at_end_of_collection,
desired_eden_length,
@@ -351,9 +371,9 @@ uint G1Policy::calculate_young_target_length(uint desired_young_length) const {
log_trace(gc, ergo, heap)("Young target length: Fully eat into reserve "
"receiving eden %u receiving additional eden %u",
receiving_eden, receiving_additional_eden);
- } else if (_free_regions_at_end_of_collection < (desired_eden_length + _reserve_regions)) {
+ } else if (_free_regions_at_end_of_collection < (desired_eden_length + reserve_regions)) {
// Partially eat into the reserve, at most max_to_eat_into_reserve regions.
- uint free_outside_reserve = _free_regions_at_end_of_collection - _reserve_regions;
+ uint free_outside_reserve = _free_regions_at_end_of_collection - reserve_regions;
assert(free_outside_reserve < desired_eden_length,
"must be %u %u",
free_outside_reserve, desired_eden_length);
@@ -569,6 +589,7 @@ void G1Policy::record_full_collection_end(size_t allocation_word_size) {
// Consider this like a collection pause for the purposes of allocation
// since last pause.
double end_sec = os::elapsedTime();
+ double start_time_sec = cur_pause_start_sec();
// "Nuke" the heuristics that control the young/mixed GC
// transitions and make sure we start with young GCs after the Full GC.
@@ -582,9 +603,6 @@ void G1Policy::record_full_collection_end(size_t allocation_word_size) {
_survivor_surv_rate_group->reset();
update_young_length_bounds();
- _old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes);
-
- double start_time_sec = cur_pause_start_sec();
record_pause(Pause::Full, start_time_sec, end_sec);
}
@@ -934,8 +952,6 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation
phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSToYoungGenCards));
}
- record_pause(collector_state()->gc_pause_type(concurrent_operation_is_full_mark), start_time_sec, end_time_sec);
-
if (collector_state()->is_in_prepare_mixed_gc()) {
assert(!collector_state()->is_in_concurrent_start_gc(),
"The young GC before mixed is not allowed to be concurrent start GC");
@@ -968,23 +984,27 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation
_free_regions_at_end_of_collection = _g1h->num_free_regions();
- _old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes);
+ Pause this_pause = collector_state()->gc_pause_type(concurrent_operation_is_full_mark);
+ size_t humongous_allocation_bytes = G1CollectedHeap::is_humongous(allocation_word_size) ?
+ G1CollectedHeap::allocation_used_bytes(allocation_word_size) : 0;
+
+ record_pause(this_pause, start_time_sec, end_time_sec, humongous_allocation_bytes);
// Do not update dynamic IHOP due to G1 periodic collection as it is highly likely
// that in this case we are not running in a "normal" operating mode.
if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) {
update_young_length_bounds();
+ // Take snapshots of these values here as update_ihop_prediction
+ // may complete the concurrent cycle and reset the values.
+ size_t non_humongous_allocation = _concurrent_cycle_tracker.non_humongous_allocated_bytes();
+ size_t peak_extra_humongous_occupancy = _concurrent_cycle_tracker.peak_extra_humongous_occupancy_bytes();
+
if (update_ihop_prediction(app_time_ms / 1000.0, is_young_only_pause)) {
- _ihop_control->report_statistics(_g1h->gc_tracer_stw(), _g1h->non_young_occupancy_after_allocation(allocation_word_size));
+ _ihop_control->report_statistics(_g1h->gc_tracer_stw(),
+ _g1h->non_young_occupancy_after_allocation(allocation_word_size),
+ non_humongous_allocation,
+ peak_extra_humongous_occupancy);
}
- } else {
- // Any garbage collection triggered as periodic collection resets the time-to-mixed
- // measurement. Periodic collection typically means that the application is "inactive", i.e.
- // the marking threads may have received an uncharacteristic amount of cpu time
- // for completing the marking, i.e. are faster than expected.
- // This skews the predicted marking length towards smaller values which might cause
- // the mark start being too late.
- abort_time_to_mixed_tracking();
}
// Note that _mmu_tracker->max_gc_time() returns the time in seconds.
@@ -1012,10 +1032,8 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation
return next_state;
}
-G1IHOPControl* G1Policy::create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker,
- const G1Predictions* predictor) {
+G1IHOPControl* G1Policy::create_ihop_control(const G1Predictions* predictor) {
return new G1IHOPControl(G1IHOP,
- old_gen_alloc_tracker,
G1UseAdaptiveIHOP,
predictor,
G1ReservePercent,
@@ -1032,29 +1050,31 @@ bool G1Policy::update_ihop_prediction(double mutator_time_s,
double const min_valid_time = 1e-6;
bool report = false;
+ if (!this_gc_was_young_only && _concurrent_cycle_tracker.has_completed_cycle()) {
+ G1ConcurrentCycleStats cycle_stats = _concurrent_cycle_tracker.get_and_reset_cycle_stats();
- if (!this_gc_was_young_only && _concurrent_start_to_mixed.has_result()) {
- double marking_to_mixed_time = _concurrent_start_to_mixed.get_and_reset_last_marking_time();
- assert(marking_to_mixed_time > 0.0,
- "Concurrent start to mixed time must be larger than zero but is %.3f",
- marking_to_mixed_time);
- if (marking_to_mixed_time > min_valid_time) {
- _ihop_control->add_marking_start_to_mixed_length(marking_to_mixed_time);
+ double concurrent_cycle_duration_s = cycle_stats._cycle_duration_s;
+ assert(concurrent_cycle_duration_s > 0.0,
+ "Time for Concurrent Start GC to the first Mixed GC must be larger than zero but is %.3f",
+ concurrent_cycle_duration_s);
+ if (concurrent_cycle_duration_s > min_valid_time) {
+ _ihop_control->record_concurrent_cycle(concurrent_cycle_duration_s,
+ cycle_stats._non_humongous_allocated_bytes,
+ cycle_stats._peak_extra_humongous_occupancy_bytes);
report = true;
}
}
- // As an approximation for the young gc promotion rates during marking we use
- // all of them. In many applications there are only a few if any young gcs during
- // marking, which makes any prediction useless. This increases the accuracy of the
- // prediction.
+ // The second clause prevents skewing the IHOP prediction with (typically) degenerate
+ // back-to-back young-gen-size samples.
if (this_gc_was_young_only && mutator_time_s > min_valid_time) {
// IHOP control wants to know the expected young gen length if it were not
// restrained by the heap reserve. Using the actual length would make the
// prediction too small and the limit the young gen every time we get to the
// predicted target occupancy.
size_t young_gen_size = young_list_desired_length() * G1HeapRegion::GrainBytes;
- _ihop_control->update_allocation_info(mutator_time_s, young_gen_size);
+
+ _ihop_control->record_expected_young_gen_size(young_gen_size);
report = true;
}
@@ -1291,11 +1311,11 @@ void G1Policy::decide_on_concurrent_start_pause() {
// Force concurrent start.
collector_state()->set_in_concurrent_start_gc();
// We might have ended up coming here about to start a mixed phase with a collection set
- // active. The following remark might change the change the "evacuation efficiency" of
+ // active. The following remark might change the "evacuation efficiency" of
// the regions in this set, leading to failing asserts later.
// Since the concurrent cycle will recreate the collection set anyway, simply drop it here.
abandon_collection_set_candidates();
- abort_time_to_mixed_tracking();
+ abort_concurrent_cycle_tracking();
log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)",
requester_for_mixed_abort(cause));
} else {
@@ -1335,7 +1355,7 @@ void G1Policy::record_concurrent_mark_cleanup_end(bool has_rebuilt_remembered_se
}
if (!mixed_gc_pending) {
- abort_time_to_mixed_tracking();
+ abort_concurrent_cycle_tracking();
log_debug(gc, ergo)("request young-only gcs (candidate old regions not available)");
}
if (mixed_gc_pending) {
@@ -1370,7 +1390,8 @@ void G1Policy::update_gc_pause_time_ratios(Pause gc_type, double start_time_sec,
void G1Policy::record_pause(Pause gc_type,
double start,
- double end) {
+ double end,
+ size_t humongous_allocation_bytes) {
// Manage the MMU tracker. For some reason it ignores Full GCs.
if (gc_type != Pause::Full) {
_mmu_tracker->add_pause(start, end);
@@ -1378,49 +1399,26 @@ void G1Policy::record_pause(Pause gc_type,
update_gc_pause_time_ratios(gc_type, start, end);
- update_time_to_mixed_tracking(gc_type, start, end);
+ size_t humongous_bytes = _g1h->humongous_regions_count() * G1HeapRegion::GrainBytes;
+ G1AllocationIntervalStats alloc_interval_stats = _old_gen_alloc_tracker.end_allocation_interval(humongous_bytes);
+ bool is_periodic_gc = _g1h->gc_cause() == GCCause::_g1_periodic_collection;
+
+ if (humongous_allocation_bytes > 0) {
+ // Record the humongous allocation that triggered the GC and attribute it to
+ // the ending allocation interval. We do this eagerly, before we know whether
+ // the post GC allocation succeeds, to keep the common case simple. In the
+ // rare case where this allocation fails, we over-account; this only
+ // affects the stored IHOP sample if the current GC is the first Mixed GC.
+ alloc_interval_stats.record_humongous_allocation(humongous_allocation_bytes);
+ }
+ _concurrent_cycle_tracker.record_allocation_interval(gc_type, is_periodic_gc, start, end, alloc_interval_stats);
double elapsed_gc_cpu_time = _analytics->gc_cpu_time_ms();
_analytics->set_gc_cpu_time_at_pause_end_ms(elapsed_gc_cpu_time);
}
-void G1Policy::update_time_to_mixed_tracking(Pause gc_type,
- double start,
- double end) {
- // Manage the mutator time tracking from concurrent start to first mixed gc.
- switch (gc_type) {
- case Pause::Full:
- abort_time_to_mixed_tracking();
- break;
- case Pause::Cleanup:
- case Pause::Remark:
- case Pause::Normal:
- case Pause::PrepareMixed:
- _concurrent_start_to_mixed.add_pause(end - start);
- break;
- case Pause::ConcurrentStartFull:
- // Do not track time-to-mixed time for periodic collections as they are likely
- // to be not representative to regular operation as the mutators are idle at
- // that time. Also only track full concurrent mark cycles.
- if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) {
- _concurrent_start_to_mixed.record_concurrent_start_end(end);
- }
- break;
- case Pause::ConcurrentStartUndo:
- assert(_g1h->gc_cause() == GCCause::_g1_humongous_allocation,
- "GC cause must be humongous allocation but is %d",
- _g1h->gc_cause());
- break;
- case Pause::Mixed:
- _concurrent_start_to_mixed.record_mixed_gc_start(start);
- break;
- default:
- ShouldNotReachHere();
- }
-}
-
-void G1Policy::abort_time_to_mixed_tracking() {
- _concurrent_start_to_mixed.reset();
+void G1Policy::abort_concurrent_cycle_tracking() {
+ _concurrent_cycle_tracker.abort_cycle();
}
bool G1Policy::next_gc_should_be_mixed() const {
diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp
index 45952963168..3cfd54c8c94 100644
--- a/src/hotspot/share/gc/g1/g1Policy.hpp
+++ b/src/hotspot/share/gc/g1/g1Policy.hpp
@@ -26,7 +26,7 @@
#define SHARE_GC_G1_G1POLICY_HPP
#include "gc/g1/g1CollectorState.hpp"
-#include "gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp"
+#include "gc/g1/g1ConcurrentCycleTracker.hpp"
#include "gc/g1/g1GCPhaseTimes.hpp"
#include "gc/g1/g1HeapRegionAttr.hpp"
#include "gc/g1/g1MMUTracker.hpp"
@@ -58,8 +58,7 @@ class STWGCTimer;
class G1Policy: public CHeapObj {
using Pause = G1CollectorState::Pause;
- static G1IHOPControl* create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker,
- const G1Predictions* predictor);
+ static G1IHOPControl* create_ihop_control(const G1Predictions* predictor);
// Update the IHOP control with the necessary statistics. Returns true if there
// has been a significant update to the prediction.
bool update_ihop_prediction(double mutator_time_s,
@@ -70,8 +69,9 @@ class G1Policy: public CHeapObj {
G1RemSetTrackingPolicy _remset_tracker;
G1MMUTracker* _mmu_tracker;
+ G1ConcurrentCycleTracker _concurrent_cycle_tracker;
// Tracking the allocation in the old generation between
- // two GCs.
+ // two pauses.
G1OldGenAllocationTracker _old_gen_alloc_tracker;
G1IHOPControl* _ihop_control;
@@ -91,9 +91,11 @@ class G1Policy: public CHeapObj {
G1SurvRateGroup* _survivor_surv_rate_group;
double _reserve_factor;
- // This will be set when the heap is expanded
- // for the first time during initialization.
- uint _reserve_regions;
+ // The allocation reserve in number of regions that we try to keep free.
+ // G1 allocation of new regions for eden is restrained when allocating into that reserve.
+ // This intentionally slows down the allocation when the heap is close to full to allow
+ // concurrent marking to finish and hopefully avoid a Full GC.
+ Atomic _reserve_regions;
G1YoungGenSizer _young_gen_sizer;
@@ -112,8 +114,6 @@ class G1Policy: public CHeapObj {
// garbage collection or the most recent refinement sweep.
size_t _to_collection_set_cards;
- G1ConcurrentStartToMixedTimeTracker _concurrent_start_to_mixed;
-
bool should_update_surv_rate_group_predictors();
double pending_cards_processing_time() const;
@@ -224,9 +224,13 @@ private:
// Calculate desired young length based on current situation without taking actually
// available free regions into account.
- uint calculate_young_desired_length(size_t pending_cards, size_t card_rs_length, size_t code_root_rs_length) const;
+ uint calculate_young_desired_length(size_t pending_cards,
+ size_t card_rs_length,
+ size_t code_root_rs_length,
+ uint min_young_length_by_sizer,
+ uint max_young_length_by_sizer) const;
// Limit the given desired young length to available free regions.
- uint calculate_young_target_length(uint desired_young_length) const;
+ uint calculate_young_target_length(uint desired_young_length, uint min_young_length_by_sizer) const;
double predict_survivor_regions_evac_time() const;
double predict_retained_regions_evac_time() const;
@@ -258,17 +262,16 @@ public:
private:
void abandon_collection_set_candidates();
- // Manage time-to-mixed tracking.
- void update_time_to_mixed_tracking(Pause gc_type, double start, double end);
// Record the given STW pause with the given start and end times (in s).
void record_pause(Pause gc_type,
double start,
- double end);
+ double end,
+ size_t humongous_allocation_bytes = 0);
void update_gc_pause_time_ratios(Pause gc_type, double start_sec, double end_sec);
// Indicate that we aborted marking before doing any mixed GCs.
- void abort_time_to_mixed_tracking();
+ void abort_concurrent_cycle_tracking();
public:
diff --git a/src/hotspot/share/gc/g1/g1Trace.cpp b/src/hotspot/share/gc/g1/g1Trace.cpp
index d6eadda5d50..bcc0941dc62 100644
--- a/src/hotspot/share/gc/g1/g1Trace.cpp
+++ b/src/hotspot/share/gc/g1/g1Trace.cpp
@@ -97,31 +97,31 @@ void G1NewTracer::report_evacuation_statistics(const G1EvacSummary& young_summar
}
void G1NewTracer::report_basic_ihop_statistics(size_t threshold,
- size_t target_ccupancy,
- size_t non_young_occupancy,
- size_t last_allocation_size,
- double last_allocation_duration,
- double last_marking_length) {
+ size_t target_occupancy,
+ size_t non_young_occupancy) {
send_basic_ihop_statistics(threshold,
- target_ccupancy,
- non_young_occupancy,
- last_allocation_size,
- last_allocation_duration,
- last_marking_length);
+ target_occupancy,
+ non_young_occupancy);
}
void G1NewTracer::report_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t current_occupancy,
size_t additional_buffer_size,
- double predicted_allocation_rate,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy,
+ double predicted_old_non_hum_alloc_rate,
+ size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active) {
send_adaptive_ihop_statistics(threshold,
internal_target_occupancy,
current_occupancy,
additional_buffer_size,
- predicted_allocation_rate,
+ non_humongous_allocation,
+ peak_extra_humongous_occupancy,
+ predicted_old_non_hum_alloc_rate,
+ predicted_peak_extra_humongous_occupancy,
predicted_marking_length,
prediction_active);
}
@@ -206,10 +206,7 @@ void G1NewTracer::send_old_evacuation_statistics(const G1EvacSummary& summary) c
void G1NewTracer::send_basic_ihop_statistics(size_t threshold,
size_t target_occupancy,
- size_t non_young_occupancy,
- size_t last_allocation_size,
- double last_allocation_duration,
- double last_marking_length) {
+ size_t non_young_occupancy) {
EventG1BasicIHOP evt;
if (evt.should_commit()) {
evt.set_gcId(GCId::current());
@@ -217,10 +214,6 @@ void G1NewTracer::send_basic_ihop_statistics(size_t threshold,
evt.set_targetOccupancy(target_occupancy);
evt.set_thresholdPercentage(target_occupancy > 0 ? ((double)threshold / target_occupancy) : 0.0);
evt.set_currentOccupancy(non_young_occupancy);
- evt.set_recentMutatorAllocationSize(last_allocation_size);
- evt.set_recentMutatorDuration(last_allocation_duration * MILLIUNITS);
- evt.set_recentAllocationRate(last_allocation_duration != 0.0 ? last_allocation_size / last_allocation_duration : 0.0);
- evt.set_lastMarkingDuration(last_marking_length * MILLIUNITS);
evt.commit();
}
}
@@ -229,7 +222,10 @@ void G1NewTracer::send_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t current_occupancy,
size_t additional_buffer_size,
- double predicted_allocation_rate,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy,
+ double predicted_old_non_hum_alloc_rate,
+ size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active) {
EventG1AdaptiveIHOP evt;
@@ -240,7 +236,10 @@ void G1NewTracer::send_adaptive_ihop_statistics(size_t threshold,
evt.set_ihopTargetOccupancy(internal_target_occupancy);
evt.set_currentOccupancy(current_occupancy);
evt.set_additionalBufferSize(additional_buffer_size);
- evt.set_predictedAllocationRate(predicted_allocation_rate);
+ evt.set_nonHumongousAllocation(non_humongous_allocation);
+ evt.set_peakExtraHumongousOccupancy(peak_extra_humongous_occupancy);
+ evt.set_predictedNonHumongousAllocation(predicted_old_non_hum_alloc_rate);
+ evt.set_predictedPeakExtraHumongousOccupancy(predicted_peak_extra_humongous_occupancy);
evt.set_predictedMarkingDuration(predicted_marking_length * MILLIUNITS);
evt.set_predictionActive(prediction_active);
evt.commit();
diff --git a/src/hotspot/share/gc/g1/g1Trace.hpp b/src/hotspot/share/gc/g1/g1Trace.hpp
index bfcc275d2ca..c5d099d5807 100644
--- a/src/hotspot/share/gc/g1/g1Trace.hpp
+++ b/src/hotspot/share/gc/g1/g1Trace.hpp
@@ -52,15 +52,15 @@ public:
void report_basic_ihop_statistics(size_t threshold,
size_t target_occupancy,
- size_t current_occupancy,
- size_t last_allocation_size,
- double last_allocation_duration,
- double last_marking_length);
+ size_t current_occupancy);
void report_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t current_occupancy,
size_t additional_buffer_size,
- double predicted_allocation_rate,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy,
+ double predicted_old_gen_non_humongous_allocation_rate,
+ size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active);
private:
@@ -73,15 +73,16 @@ private:
void send_basic_ihop_statistics(size_t threshold,
size_t target_occupancy,
- size_t non_young_occupancy,
- size_t last_allocation_size,
- double last_allocation_duration,
- double last_marking_length);
+ size_t non_young_occupancy);
+
void send_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t non_young_occupancy,
size_t additional_buffer_size,
- double predicted_allocation_rate,
+ size_t non_humongous_allocation,
+ size_t peak_extra_humongous_occupancy,
+ double predicted_old_gen_non_humongous_allocation_rate,
+ size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active);
};
diff --git a/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp b/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp
index e1203229557..f88736c5642 100644
--- a/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp
+++ b/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -58,9 +58,9 @@ void G1UncommitRegionTask::enqueue() {
G1UncommitRegionTask* uncommit_task = instance();
if (!uncommit_task->is_active()) {
- // Change state to active and schedule using UncommitInitialDelayMs.
+ // Change state to active and schedule.
uncommit_task->set_active(true);
- G1CollectedHeap::heap()->service_thread()->schedule_task(uncommit_task, UncommitInitialDelayMs);
+ G1CollectedHeap::heap()->service_thread()->schedule_task(uncommit_task, G1UncommitInitialDelay);
}
}
diff --git a/src/hotspot/share/gc/g1/g1UncommitRegionTask.hpp b/src/hotspot/share/gc/g1/g1UncommitRegionTask.hpp
index 7c9a25f6857..835217d2a59 100644
--- a/src/hotspot/share/gc/g1/g1UncommitRegionTask.hpp
+++ b/src/hotspot/share/gc/g1/g1UncommitRegionTask.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -34,8 +34,6 @@ class G1UncommitRegionTask : public G1ServiceTask {
// This limit is small enough to ensure that the duration of each invocation
// is short, while still making reasonable progress.
static const uint UncommitSizeLimit = 128 * M;
- // Initial delay in milliseconds after GC before the regions are uncommitted.
- static const uint UncommitInitialDelayMs = 100;
// The delay between two uncommit task executions.
static const uint UncommitTaskDelayMs = 10;
diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp
index d0c843aa5d6..bf4a6cca81d 100644
--- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp
+++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp
@@ -542,7 +542,7 @@ public:
class FreeCSetStats {
size_t _before_used_bytes; // Usage in regions successfully evacuate
size_t _after_used_bytes; // Usage in regions failing evacuation
- size_t _bytes_allocated_in_old_since_last_gc; // Size of young regions turned into old
+ size_t _bytes_allocated_in_old_since_last_pause; // Size of young regions turned into old
size_t _failure_used_words; // Live size in failed regions
size_t _failure_waste_words; // Wasted size in failed regions
uint _regions_freed; // Number of regions freed
@@ -551,7 +551,7 @@ public:
FreeCSetStats() :
_before_used_bytes(0),
_after_used_bytes(0),
- _bytes_allocated_in_old_since_last_gc(0),
+ _bytes_allocated_in_old_since_last_pause(0),
_failure_used_words(0),
_failure_waste_words(0),
_regions_freed(0) { }
@@ -560,7 +560,7 @@ public:
assert(other != nullptr, "invariant");
_before_used_bytes += other->_before_used_bytes;
_after_used_bytes += other->_after_used_bytes;
- _bytes_allocated_in_old_since_last_gc += other->_bytes_allocated_in_old_since_last_gc;
+ _bytes_allocated_in_old_since_last_pause += other->_bytes_allocated_in_old_since_last_pause;
_failure_used_words += other->_failure_used_words;
_failure_waste_words += other->_failure_waste_words;
_regions_freed += other->_regions_freed;
@@ -575,7 +575,7 @@ public:
g1h->alloc_buffer_stats(G1HeapRegionAttr::Old)->add_failure_used_and_waste(_failure_used_words, _failure_waste_words);
G1Policy *policy = g1h->policy();
- policy->old_gen_alloc_tracker()->add_allocated_bytes_since_last_gc(_bytes_allocated_in_old_since_last_gc);
+ policy->old_gen_alloc_tracker()->add_allocated_non_humongous_bytes(_bytes_allocated_in_old_since_last_pause);
policy->cset_regions_freed();
}
@@ -592,7 +592,7 @@ public:
// additional allocation: both the objects still in the region and the
// ones already moved are accounted for elsewhere.
if (r->is_young()) {
- _bytes_allocated_in_old_since_last_gc += G1HeapRegion::GrainBytes;
+ _bytes_allocated_in_old_since_last_pause += G1HeapRegion::GrainBytes;
}
}
diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp
index ffa573c68cc..60c79ec28df 100644
--- a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp
+++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -30,7 +30,7 @@
#include "runtime/globals_extension.hpp"
G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults),
- _use_adaptive_sizing(true), _min_desired_young_length(0), _max_desired_young_length(0) {
+ _use_adaptive_sizing(true), _min_desired_young_length(), _max_desired_young_length(0) {
precond(!FLAG_IS_ERGO(NewRatio));
precond(!FLAG_IS_ERGO(NewSize));
@@ -100,16 +100,16 @@ G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults),
}
if (user_specified_NewSize) {
- _min_desired_young_length = MAX2((uint)(NewSize / G1HeapRegion::GrainBytes), 1U);
+ _min_desired_young_length.store_relaxed(MAX2((uint)(NewSize / G1HeapRegion::GrainBytes), 1U));
}
if (user_specified_MaxNewSize) {
- _max_desired_young_length = MAX2((uint)(MaxNewSize / G1HeapRegion::GrainBytes), 1U);
+ _max_desired_young_length.store_relaxed(MAX2((uint)(MaxNewSize / G1HeapRegion::GrainBytes), 1U));
}
if (user_specified_NewSize && user_specified_MaxNewSize) {
_sizer_kind = SizerMaxAndNewSize;
- _use_adaptive_sizing = _min_desired_young_length != _max_desired_young_length;
+ _use_adaptive_sizing = min_desired_young_length() != max_desired_young_length();
} else if (user_specified_NewSize) {
_sizer_kind = SizerNewSizeOnly;
} else {
@@ -159,20 +159,22 @@ void G1YoungGenSizer::recalculate_min_max_young_length(uint number_of_heap_regio
}
void G1YoungGenSizer::adjust_max_new_size(uint number_of_heap_regions) {
-
// We need to pass the desired values because recalculation may not update these
// values in some cases.
- uint temp = _min_desired_young_length;
- uint result = _max_desired_young_length;
- recalculate_min_max_young_length(number_of_heap_regions, &temp, &result);
+ uint unused_new_min = min_desired_young_length();
+ uint new_max = max_desired_young_length();
+ recalculate_min_max_young_length(number_of_heap_regions, &unused_new_min, &new_max);
- size_t max_young_size = result * G1HeapRegion::GrainBytes;
+ size_t max_young_size = new_max * G1HeapRegion::GrainBytes;
if (max_young_size != MaxNewSize) {
FLAG_SET_ERGO(MaxNewSize, max_young_size);
}
}
void G1YoungGenSizer::heap_size_changed(uint new_number_of_heap_regions) {
- recalculate_min_max_young_length(new_number_of_heap_regions, &_min_desired_young_length,
- &_max_desired_young_length);
+ uint min = min_desired_young_length();
+ uint max = max_desired_young_length();
+ recalculate_min_max_young_length(new_number_of_heap_regions, &min, &max);
+ _min_desired_young_length.store_relaxed(min);
+ _max_desired_young_length.store_relaxed(max);
}
diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp
index 138989d4d94..c60c3c373a9 100644
--- a/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp
+++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,7 @@
#ifndef SHARE_GC_G1_G1YOUNGGENSIZER_HPP
#define SHARE_GC_G1_G1YOUNGGENSIZER_HPP
+#include "runtime/atomic.hpp"
#include "utilities/globalDefinitions.hpp"
// There are three command line options related to the young gen size:
@@ -78,8 +79,8 @@ private:
// true otherwise.
bool _use_adaptive_sizing;
- uint _min_desired_young_length;
- uint _max_desired_young_length;
+ Atomic _min_desired_young_length;
+ Atomic _max_desired_young_length;
uint calculate_default_min_length(uint new_number_of_heap_regions);
uint calculate_default_max_length(uint new_number_of_heap_regions);
@@ -96,10 +97,10 @@ public:
virtual void heap_size_changed(uint new_number_of_heap_regions);
uint min_desired_young_length() const {
- return _min_desired_young_length;
+ return _min_desired_young_length.load_relaxed();
}
uint max_desired_young_length() const {
- return _max_desired_young_length;
+ return _max_desired_young_length.load_relaxed();
}
bool use_adaptive_young_list_length() const {
diff --git a/src/hotspot/share/gc/g1/g1_globals.hpp b/src/hotspot/share/gc/g1/g1_globals.hpp
index 14daac4800b..8a346c5c78f 100644
--- a/src/hotspot/share/gc/g1/g1_globals.hpp
+++ b/src/hotspot/share/gc/g1/g1_globals.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -184,6 +184,10 @@
"shrink attempt.") \
range(0, 100) \
\
+ develop(uint, G1UncommitInitialDelay, 100, \
+ "Delay in milliseconds until regions just made eligible for " \
+ "uncommit are actually uncommitted.") \
+ \
product(uint, G1CPUUsageDeviationPercent, 25, DIAGNOSTIC, \
"The acceptable deviation (in percent) from the target GC CPU " \
"usage (based on GCTimeRatio). Creates a tolerance range " \
diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp
index a4a2bfe72c2..ff757f205a2 100644
--- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp
+++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp
@@ -1268,8 +1268,7 @@ void PSParallelCompact::marking_phase(ParallelOldTracer *gc_tracer) {
#endif
}
-template
-void PSParallelCompact::adjust_in_space_helper(SpaceId id, Atomic* claim_counter, Func&& on_stripe) {
+void PSParallelCompact::adjust_in_space_helper(SpaceId id, Atomic* claim_counter) {
MutableSpace* sp = PSParallelCompact::space(id);
HeapWord* const bottom = sp->bottom();
HeapWord* const top = sp->top();
@@ -1288,53 +1287,46 @@ void PSParallelCompact::adjust_in_space_helper(SpaceId id, Atomic* claim_c
break;
}
HeapWord* stripe_end = MIN2(cur_stripe + stripe_size, top);
- on_stripe(cur_stripe, stripe_end);
+ adjust_in_stripe(cur_stripe, stripe_end);
+ }
+}
+
+size_t PSParallelCompact::adjust_in_obj_with_limit(HeapWord* obj_start, HeapWord* left, HeapWord* right) {
+ precond(mark_bitmap()->is_marked(obj_start));
+ oop obj = cast_to_oop(obj_start);
+ return obj->oop_iterate_size(&pc_adjust_pointer_closure, MemRegion(left, right));
+}
+
+void PSParallelCompact::adjust_in_stripe(HeapWord* stripe_start, HeapWord* stripe_end) {
+ precond(_summary_data.is_region_aligned(stripe_start));
+
+ RegionData* cur_region = _summary_data.addr_to_region_ptr(stripe_start);
+ HeapWord* obj_start;
+ if (cur_region->partial_obj_size() != 0) {
+ obj_start = cur_region->partial_obj_addr();
+ obj_start += adjust_in_obj_with_limit(obj_start, stripe_start, stripe_end);
+ } else {
+ obj_start = stripe_start;
+ }
+
+ while (obj_start < stripe_end) {
+ obj_start = mark_bitmap()->find_obj_beg(obj_start, stripe_end);
+ if (obj_start >= stripe_end) {
+ break;
+ }
+ obj_start += adjust_in_obj_with_limit(obj_start, stripe_start, stripe_end);
}
}
void PSParallelCompact::adjust_in_old_space(Atomic* claim_counter) {
// Regions in old-space shouldn't be split.
- assert(!_space_info[old_space_id].split_info().is_valid(), "inv");
+ precond(!_space_info[old_space_id].split_info().is_valid());
- auto scan_obj_with_limit = [&] (HeapWord* obj_start, HeapWord* left, HeapWord* right) {
- assert(mark_bitmap()->is_marked(obj_start), "inv");
- oop obj = cast_to_oop(obj_start);
- return obj->oop_iterate_size(&pc_adjust_pointer_closure, MemRegion(left, right));
- };
-
- adjust_in_space_helper(old_space_id, claim_counter, [&] (HeapWord* stripe_start, HeapWord* stripe_end) {
- assert(_summary_data.is_region_aligned(stripe_start), "inv");
- RegionData* cur_region = _summary_data.addr_to_region_ptr(stripe_start);
- HeapWord* obj_start;
- if (cur_region->partial_obj_size() != 0) {
- obj_start = cur_region->partial_obj_addr();
- obj_start += scan_obj_with_limit(obj_start, stripe_start, stripe_end);
- } else {
- obj_start = stripe_start;
- }
-
- while (obj_start < stripe_end) {
- obj_start = mark_bitmap()->find_obj_beg(obj_start, stripe_end);
- if (obj_start >= stripe_end) {
- break;
- }
- obj_start += scan_obj_with_limit(obj_start, stripe_start, stripe_end);
- }
- });
+ adjust_in_space_helper(old_space_id, claim_counter);
}
void PSParallelCompact::adjust_in_young_space(SpaceId id, Atomic* claim_counter) {
- adjust_in_space_helper(id, claim_counter, [](HeapWord* stripe_start, HeapWord* stripe_end) {
- HeapWord* obj_start = stripe_start;
- while (obj_start < stripe_end) {
- obj_start = mark_bitmap()->find_obj_beg(obj_start, stripe_end);
- if (obj_start >= stripe_end) {
- break;
- }
- oop obj = cast_to_oop(obj_start);
- obj_start += obj->oop_iterate_size(&pc_adjust_pointer_closure);
- }
- });
+ adjust_in_space_helper(id, claim_counter);
}
void PSParallelCompact::adjust_pointers_in_spaces(uint worker_id, Atomic* claim_counters) {
diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp
index 25f4f66de6f..909005f5360 100644
--- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp
+++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp
@@ -760,8 +760,11 @@ public:
// should_do_max_compaction controls whether all spaces for dead objs should be reclaimed.
static bool invoke(bool clear_all_soft_refs, bool should_do_max_compaction);
- template
- static void adjust_in_space_helper(SpaceId id, Atomic* claim_counter, Func&& on_stripe);
+ static void adjust_in_space_helper(SpaceId id, Atomic* claim_counter);
+
+ static size_t adjust_in_obj_with_limit(HeapWord* obj_start, HeapWord* left, HeapWord* right);
+
+ static void adjust_in_stripe(HeapWord* stripe_start, HeapWord* stripe_end);
static void adjust_in_old_space(Atomic* claim_counter);
diff --git a/src/hotspot/share/gc/serial/cardTableRS.cpp b/src/hotspot/share/gc/serial/cardTableRS.cpp
index a53ab066387..fe4977a809a 100644
--- a/src/hotspot/share/gc/serial/cardTableRS.cpp
+++ b/src/hotspot/share/gc/serial/cardTableRS.cpp
@@ -49,7 +49,14 @@ void CardTableRS::scan_old_to_young_refs(TenuredGeneration* tg, HeapWord* saved_
void CardTableRS::maintain_old_to_young_invariant(TenuredGeneration* old_gen,
bool is_young_gen_empty) {
if (is_young_gen_empty) {
- clear_MemRegion(old_gen->prev_used_region());
+ MemRegion prev_used_mr = old_gen->prev_used_region();
+ if (!prev_used_mr.is_empty()) {
+ clear_MemRegion(prev_used_mr);
+ }
+ {
+ MemRegion old_gen_committed{old_gen->space()->bottom(), old_gen->space()->end()};
+ verify_region(old_gen_committed, clean_card_val(), true);
+ }
} else {
MemRegion used_mr = old_gen->used_region();
MemRegion prev_used_mr = old_gen->prev_used_region();
@@ -59,7 +66,9 @@ void CardTableRS::maintain_old_to_young_invariant(TenuredGeneration* old_gen,
}
// No idea which card contains old-to-young pointer, so dirtying cards for
// the entire used part of old-gen conservatively.
- dirty_MemRegion(used_mr);
+ if (!used_mr.is_empty()) {
+ dirty_MemRegion(used_mr);
+ }
}
}
diff --git a/src/hotspot/share/gc/shared/cardTable.cpp b/src/hotspot/share/gc/shared/cardTable.cpp
index e6e3fdf3d82..dcf13ab48e9 100644
--- a/src/hotspot/share/gc/shared/cardTable.cpp
+++ b/src/hotspot/share/gc/shared/cardTable.cpp
@@ -194,6 +194,7 @@ void CardTable::resize_covered_region(MemRegion new_region) {
// Note that these versions are precise! The scanning code has to handle the
// fact that the write barrier may be either precise or imprecise.
void CardTable::dirty_MemRegion(MemRegion mr) {
+ assert(!mr.is_empty(), "precondition");
assert(align_down(mr.start(), HeapWordSize) == mr.start(), "Unaligned start");
assert(align_up (mr.end(), HeapWordSize) == mr.end(), "Unaligned end" );
assert(_covered[0].contains(mr) || _covered[1].contains(mr), "precondition");
@@ -203,6 +204,7 @@ void CardTable::dirty_MemRegion(MemRegion mr) {
}
void CardTable::clear_MemRegion(MemRegion mr) {
+ assert(!mr.is_empty(), "precondition");
// Be conservative: only clean cards entirely contained within the
// region.
CardValue* cur;
diff --git a/src/hotspot/share/gc/shared/cardTableBarrierSet.inline.hpp b/src/hotspot/share/gc/shared/cardTableBarrierSet.inline.hpp
index f60a7f47a19..5c10a1618a5 100644
--- a/src/hotspot/share/gc/shared/cardTableBarrierSet.inline.hpp
+++ b/src/hotspot/share/gc/shared/cardTableBarrierSet.inline.hpp
@@ -130,7 +130,10 @@ oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw,
// 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);
+ if (pd > 0) {
+ // Copied at least one element; call the barrier.
+ bs->write_ref_array((HeapWord*)dst_raw, pd);
+ }
return OopCopyResult::failed_check_class_cast;
}
}
diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp
index 1ff6fd493a7..14bc320e32c 100644
--- a/src/hotspot/share/gc/shared/gc_globals.hpp
+++ b/src/hotspot/share/gc/shared/gc_globals.hpp
@@ -199,10 +199,9 @@
range(1, (INT_MAX - 1)) \
\
product(size_t, ReferencesPerThread, 1000, EXPERIMENTAL, \
- "Ergonomically start one thread for this amount of " \
- "references for reference processing if " \
- "ParallelRefProcEnabled is true. Specify 0 to disable and " \
- "use all threads.") \
+ "Ergonomically start one thread for this amount of references " \
+ "for reference processing for parallel stop-the-world garbage " \
+ "collectors. Specify 0 to force use of all available threads.") \
\
product(uint, InitiatingHeapOccupancyPercent, 45, \
"The percent occupancy (IHOP) of the current old generation " \
diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp
index 66032944251..e4f1b6e86c1 100644
--- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp
+++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp
@@ -27,7 +27,6 @@
#include "gc/shared/barrierSet.hpp"
#include "gc/shenandoah/c2/shenandoahBarrierSetC2.hpp"
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
-#include "gc/shenandoah/shenandoahForwarding.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
#include "gc/shenandoah/shenandoahRuntime.hpp"
#include "gc/shenandoah/shenandoahThreadLocalData.hpp"
@@ -712,10 +711,9 @@ void ShenandoahBarrierSetC2::print_barrier_data(outputStream* os, uint8_t data)
fatal("Unknown bit!");
}
- os->print_cr(" GC configuration: %sLRB %sSATB %sCAS %sClone %sCard",
+ os->print_cr(" GC configuration: %sLRB %sSATB %sClone %sCard",
(ShenandoahLoadRefBarrier ? "+" : "-"),
(ShenandoahSATBBarrier ? "+" : "-"),
- (ShenandoahCASBarrier ? "+" : "-"),
(ShenandoahCloneBarrier ? "+" : "-"),
(ShenandoahCardBarrier ? "+" : "-")
);
@@ -912,16 +910,16 @@ void ShenandoahBarrierStubC2::load_post(MacroAssembler* masm, const MachNode* no
}
}
-void ShenandoahBarrierStubC2::store_pre(MacroAssembler* masm, const MachNode* node, Register obj, Address addr, Register tmp1, Register tmp2, bool narrow) {
+void ShenandoahBarrierStubC2::store_pre(MacroAssembler* masm, const MachNode* node, Address addr, Register tmp1, Register tmp2, Register tmp3, bool narrow) {
// Store pre-barrier: SATB, keep-alive the current memory value.
if (needs_slow_barrier(node)) {
assert(!needs_load_ref_barrier(node), "Should not be required for stores");
- ShenandoahBarrierStubC2* const stub = create(node, obj, addr, tmp1, tmp2, narrow, /* do_load = */ true);
+ ShenandoahBarrierStubC2* const stub = create(node, tmp1, addr, tmp2, tmp3, narrow, /* do_load = */ true);
stub->enter_if_gc_state(*masm, ShenandoahHeap::MARKING, tmp1);
}
}
-void ShenandoahBarrierStubC2::load_store_pre(MacroAssembler* masm, const MachNode* node, Register obj, Address addr, Register tmp1, Register tmp2, bool narrow) {
+void ShenandoahBarrierStubC2::load_store_pre(MacroAssembler* masm, const MachNode* node, Address addr, Register tmp1, Register tmp2, Register tmp3, bool narrow) {
// Load/Store pre-barrier:
// a. Avoids false positives from CAS encountering to-space memory values.
// b. Satisfies the need for LRB for the CAE result.
@@ -930,7 +928,7 @@ void ShenandoahBarrierStubC2::load_store_pre(MacroAssembler* masm, const MachNod
// (a) and (b) are covered because load barrier does memory location fixup.
// (c) is covered by KA on the current memory value.
if (needs_slow_barrier(node)) {
- ShenandoahBarrierStubC2* const stub = create(node, obj, addr, tmp1, tmp2, narrow, /* do_load = */ true);
+ ShenandoahBarrierStubC2* const stub = create(node, tmp1, addr, tmp2, tmp3, narrow, /* do_load = */ true);
char check = 0;
check |= needs_keep_alive_barrier(node) ? ShenandoahHeap::MARKING : 0;
check |= needs_load_ref_barrier(node) ? ShenandoahHeap::HAS_FORWARDED : 0;
diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp
index d18ebe26853..11fdbd8674b 100644
--- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp
+++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp
@@ -228,10 +228,10 @@ public:
return needs_load_ref_barrier(node) || needs_keep_alive_barrier(node);
}
- static void load_post(MacroAssembler* masm, const MachNode* node, Register obj, Address addr, Register tmp1, Register tmp2, bool narrow);
- static void store_pre(MacroAssembler* masm, const MachNode* node, Register obj, Address addr, Register tmp1, Register tmp2, bool narrow);
- static void store_post(MacroAssembler* masm, const MachNode* node, Address addr, Register tmp1, Register tmp2);
- static void load_store_pre(MacroAssembler* masm, const MachNode* node, Register obj, Address addr, Register tmp1, Register tmp2, bool narrow);
+ static void load_post(MacroAssembler* masm, const MachNode* node, Register obj, Address addr, Register tmp1, Register tmp2, bool narrow);
+ static void store_pre(MacroAssembler* masm, const MachNode* node, Address addr, Register tmp1, Register tmp2, Register tmp3, bool narrow);
+ static void store_post(MacroAssembler* masm, const MachNode* node, Address addr, Register tmp1, Register tmp2);
+ static void load_store_pre(MacroAssembler* masm, const MachNode* node, Address addr, Register tmp1, Register tmp2, Register tmp3, bool narrow);
static void load_store_post(MacroAssembler* masm, const MachNode* node, Address addr, Register tmp1, Register tmp2);
void emit_code(MacroAssembler& masm);
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
index c880af7fd49..ca4dfc71c61 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
@@ -358,7 +358,7 @@ size_t ShenandoahGenerationalHeuristics::select_aged_regions(ShenandoahInPlacePr
// Having chosen the collection set, adjust the budgets for generational mode based on its composition. Note
// that young_generation->available() now knows about recently discovered immediate garbage.
-void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap* const heap,
+void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahGenerationalHeap* const heap,
ShenandoahCollectionSet* const collection_set) {
shenandoah_assert_generational();
// We may find that old_evacuation_reserve and/or loaned_for_young_evacuation are not fully consumed, in which case we may
@@ -481,6 +481,16 @@ void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap*
if (add_regions_to_young > 0) {
assert(excess_old >= add_regions_to_young * region_size_bytes, "Cannot xfer more than excess old");
+ if (heap->age_census()->is_always_tenure()) {
+ // Cap excess_old at one min-PLAB per worker so this much stays in old's promotion reserve
+ // instead of being transferred to young.
+ const size_t min_plab_total = heap->plab_min_size() * HeapWordSize * heap->workers()->max_workers();
+ if (excess_old > min_plab_total) {
+ excess_old = min_plab_total;
+ // Avoid underflowing excess_old when we subtract below.
+ add_regions_to_young = 0;
+ }
+ }
excess_old -= add_regions_to_young * region_size_bytes;
log_debug(gc, ergo)("Before start of evacuation, total_promotion reserve is young_advance_promoted_reserve: %zu "
"plus excess: old: %zu", young_advance_promoted_reserve_used, excess_old);
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp
index a0e4ab78d5c..1860e3d4c0f 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp
@@ -92,7 +92,7 @@ private:
// Adjust evacuation budgets after choosing collection set. On entry, the instance variable _regions_to_xfer
// represents regions to be transferred to old based on decisions made in top_off_collection_set()
- void adjust_evacuation_budgets(ShenandoahHeap* const heap,
+ void adjust_evacuation_budgets(ShenandoahGenerationalHeap* const heap,
ShenandoahCollectionSet* const collection_set);
protected:
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
index e4ac576aa6f..d9f3bdee828 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
@@ -179,6 +179,12 @@ void ShenandoahGlobalHeuristics::choose_global_collection_set(ShenandoahCollecti
size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + original_young_evac_reserve;
size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0;
+ // Admit every region with any garbage so every live object gets a chance to be promoted.
+ if (heap->age_census()->is_always_tenure()) {
+ ignore_threshold = 0;
+ min_garbage = SIZE_MAX;
+ }
+
ShenandoahGlobalCSetBudget budget(region_size_bytes,
shared_reserve_regions * region_size_bytes,
garbage_threshold, ignore_threshold, min_garbage,
diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp
index 79c4ecabcf4..5a21ded6d29 100644
--- a/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp
+++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp
@@ -57,7 +57,6 @@ void ShenandoahGenerationalMode::initialize_flags() const {
// Final configuration checks
SHENANDOAH_CHECK_FLAG_SET(ShenandoahLoadRefBarrier);
SHENANDOAH_CHECK_FLAG_SET(ShenandoahSATBBarrier);
- SHENANDOAH_CHECK_FLAG_SET(ShenandoahCASBarrier);
SHENANDOAH_CHECK_FLAG_SET(ShenandoahCloneBarrier);
SHENANDOAH_CHECK_FLAG_SET(ShenandoahCardBarrier);
}
diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
index cc098bc5a21..31ffcd623fd 100644
--- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
+++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
@@ -48,7 +48,6 @@ void ShenandoahPassiveMode::initialize_flags() const {
// Disable known barriers by default.
SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahLoadRefBarrier);
SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahSATBBarrier);
- SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahCASBarrier);
SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahCloneBarrier);
SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahCardBarrier);
}
diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp
index e27aa90542d..842c7841b9c 100644
--- a/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp
+++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp
@@ -40,7 +40,6 @@ void ShenandoahSATBMode::initialize_flags() const {
// Final configuration checks
SHENANDOAH_CHECK_FLAG_SET(ShenandoahLoadRefBarrier);
SHENANDOAH_CHECK_FLAG_SET(ShenandoahSATBBarrier);
- SHENANDOAH_CHECK_FLAG_SET(ShenandoahCASBarrier);
SHENANDOAH_CHECK_FLAG_SET(ShenandoahCloneBarrier);
SHENANDOAH_CHECK_FLAG_UNSET(ShenandoahCardBarrier);
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAffiliation.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAffiliation.hpp
index 6b3c846f5fe..3dc8becfb62 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAffiliation.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAffiliation.hpp
@@ -25,6 +25,8 @@
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHAFFILIATION_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHAFFILIATION_HPP
+#include "utilities/debug.hpp"
+
enum ShenandoahAffiliation {
FREE,
YOUNG_GENERATION,
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
index 4989c929b32..8fa497802fd 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
@@ -34,7 +34,7 @@ ShenandoahAgeCensus::ShenandoahAgeCensus()
}
ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers)
- : _max_workers(max_workers)
+ : _max_workers(max_workers), _always_tenure(false)
{
if (ShenandoahGenerationalMinTenuringAge > ShenandoahGenerationalMaxTenuringAge) {
vm_exit_during_initialization(
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
index c140f445e21..5636dee3ae2 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
@@ -121,6 +121,8 @@ class ShenandoahAgeCensus: public CHeapObj {
uint _max_workers; // Maximum number of workers for parallel tasks
+ bool _always_tenure; // When true, every age is tenurable.
+
// Mortality rate of a cohort, given its population in
// previous and current epochs
double mortality_rate(size_t prev_pop, size_t cur_pop);
@@ -150,9 +152,9 @@ class ShenandoahAgeCensus: public CHeapObj {
return _tenuring_threshold[prev];
}
- // Override the tenuring threshold for the current epoch. This is used to
- // cause everything to be promoted for a whitebox full gc request.
- void set_tenuring_threshold(uint threshold) { _tenuring_threshold[_epoch] = threshold; }
+ // Set always tenure mode. Currently only used by ShenandoahTenuringOverride
+ // to force is_tenurable() to be true for every age during WB.fullGC tests.
+ void set_always_tenure(bool always_tenure) { _always_tenure = always_tenure; }
#ifndef PRODUCT
// Return the sum of size of objects of all ages recorded in the
@@ -187,11 +189,13 @@ class ShenandoahAgeCensus: public CHeapObj {
// Visible for testing. Use is_tenurable for consistent tenuring comparisons.
uint tenuring_threshold() const { return _tenuring_threshold[_epoch]; }
- // Return true if this age is at or above the tenuring threshold.
+ // Return true if this age is at or above the tenuring threshold, or if always tenure is enabled.
bool is_tenurable(uint age) const {
- return age >= tenuring_threshold();
+ return age >= tenuring_threshold() || _always_tenure;
}
+ bool is_always_tenure() const { return _always_tenure; }
+
// Update the local age table for worker_id by size for
// given obj_age, region_age, and region_youth
CENSUS_NOISE(void add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id);)
@@ -244,24 +248,22 @@ class ShenandoahAgeCensus: public CHeapObj {
void print();
};
-// RAII object that temporarily overrides the tenuring threshold for the
-// duration of a scope, restoring the original value on destruction.
-// Used to force promotion of all young objects during whitebox full GCs.
+// RAII object that enables ShenandoahAgeCensus always tenure mode for the
+// duration of a scope and disables it on destruction. Used to force promotion
+// of all young objects during whitebox full GCs.
class ShenandoahTenuringOverride : public StackObj {
ShenandoahAgeCensus* _census;
- uint _saved_threshold;
bool _active;
public:
ShenandoahTenuringOverride(bool active, ShenandoahAgeCensus* census) :
- _census(census), _saved_threshold(0), _active(active) {
+ _census(census), _active(active) {
if (_active) {
- _saved_threshold = _census->tenuring_threshold();
- _census->set_tenuring_threshold(0);
+ _census->set_always_tenure(true);
}
}
~ShenandoahTenuringOverride() {
if (_active) {
- _census->set_tenuring_threshold(_saved_threshold);
+ _census->set_always_tenure(false);
}
}
};
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
index 3ccb7cc336b..6d0895c5afa 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
@@ -63,7 +63,6 @@ void ShenandoahArguments::initialize() {
FLAG_SET_DEFAULT(ShenandoahSATBBarrier, false);
FLAG_SET_DEFAULT(ShenandoahLoadRefBarrier, false);
- FLAG_SET_DEFAULT(ShenandoahCASBarrier, false);
FLAG_SET_DEFAULT(ShenandoahCardBarrier, false);
FLAG_SET_DEFAULT(ShenandoahCloneBarrier, false);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
index 9ab45380c61..976a505c713 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
@@ -253,4 +253,16 @@ public:
};
#endif // ASSERT
+class ShenandoahMultiThreadClosure : public ThreadClosure {
+ ThreadClosure& _cl1;
+ ThreadClosure& _cl2;
+public:
+ ShenandoahMultiThreadClosure(ThreadClosure& cl1, ThreadClosure& cl2) :
+ _cl1(cl1), _cl2(cl2) {}
+ inline void do_thread(Thread* thread) override {
+ _cl1.do_thread(thread);
+ _cl2.do_thread(thread);
+ }
+};
+
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHCLOSURES_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp
index cfa79fc055e..e3267517e05 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp
@@ -205,8 +205,15 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const {
out->print_cr("enough regions with no live objects to skip evacuation.");
out->cr();
+ size_t gc_attempts = 0;
+ for (int c = 0; c < GCCause::_last_gc_cause; c++) {
+ gc_attempts += _collection_cause_counts[c];
+ }
+
size_t completed_gcs = _success_full_gcs + _success_degenerated_gcs + _success_concurrent_gcs + _success_old_gcs;
- out->print_cr("%5zu Completed GCs", completed_gcs);
+ size_t cancelled_gcs = gc_attempts - completed_gcs;
+ out->print_cr("%5zu GC attempts. %zu Completed GCs (%.2f%%).",
+ gc_attempts, completed_gcs, percent_of(completed_gcs, gc_attempts));
size_t explicit_requests = 0;
size_t implicit_requests = 0;
@@ -220,7 +227,7 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const {
implicit_requests += cause_count;
}
const char* desc = GCCause::to_string(cause);
- out->print_cr(" %5zu caused by %s (%.2f%%)", cause_count, desc, percent_of(cause_count, completed_gcs));
+ out->print_cr(" %5zu caused by %s (%.2f%%)", cause_count, desc, percent_of(cause_count, gc_attempts));
}
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
index 07eb653bc94..08032b224d0 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
@@ -1203,19 +1203,13 @@ void ShenandoahConcurrentGC::op_final_update_refs() {
heap->verifier()->verify_roots_in_to_space(_generation);
}
- // If we are running in generational mode and this is an aging cycle, this will also age active
- // regions that haven't been used for allocation.
+ // If we are running in generational mode, this will also age active regions that
+ // haven't been used for allocation.
heap->update_heap_region_states(true /*concurrent*/);
heap->set_update_refs_in_progress(false);
heap->set_has_forwarded_objects(false);
- if (heap->mode()->is_generational() && heap->is_concurrent_old_mark_in_progress()) {
- // Aging_cycle is only relevant during evacuation cycle for individual objects and during final mark for
- // entire regions. Both of these relevant operations occur before final update refs.
- ShenandoahGenerationalHeap::heap()->set_aging_cycle(false);
- }
-
if (ShenandoahVerify) {
ShenandoahTimingsTracker v(ShenandoahPhaseTimings::final_update_refs_verify);
heap->verifier()->verify_after_update_refs(_generation);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp
index 4db8821399f..31ffbc817f1 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp
@@ -59,6 +59,10 @@ public:
ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_mark, ShenandoahPhaseTimings::Work, worker_id, true);
SuspendibleThreadSetJoiner stsj;
_cm->mark_loop(worker_id, _terminator, GENERATION, true /*cancellable*/);
+ // Concurrent marking loop flushes Java thread buffers, coordinating with a handshake.
+ // Here, a GC worker has completed marking work, so it is a good time to flush its SATB buffers too.
+ SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set();
+ satb_mq_set.flush_queue(ShenandoahThreadLocalData::satb_mark_queue(Thread::current()));
}
};
@@ -67,32 +71,14 @@ class ShenandoahFinalMarkingTask : public WorkerTask {
private:
ShenandoahConcurrentMark* _cm;
TaskTerminator* _terminator;
- ThreadsClaimTokenScope _threads_claim_token_scope; // needed for Threads::possibly_parallel_threads_do
public:
ShenandoahFinalMarkingTask(ShenandoahConcurrentMark* cm, TaskTerminator* terminator) :
- WorkerTask("Shenandoah Final Mark"), _cm(cm), _terminator(terminator),
- _threads_claim_token_scope() {
- }
+ WorkerTask("Shenandoah Final Mark"), _cm(cm), _terminator(terminator) {}
void work(uint worker_id) {
- ShenandoahHeap* heap = ShenandoahHeap::heap();
-
ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::finish_mark, ShenandoahPhaseTimings::Work, worker_id, true);
ShenandoahParallelWorkerSession worker_session(worker_id);
- // First drain remaining SATB buffers.
- {
- ShenandoahObjToScanQueue* q = _cm->get_queue(worker_id);
- ShenandoahObjToScanQueue* old_q = _cm->get_old_queue(worker_id);
-
- ShenandoahSATBBufferClosure cl(q, old_q);
- SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set();
- while (satb_mq_set.apply_closure_to_completed_buffer(&cl)) {}
- assert(!heap->has_forwarded_objects(), "Not expected");
-
- ShenandoahFlushSATB tc(satb_mq_set);
- Threads::possibly_parallel_threads_do(true /* is_par */, &tc);
- }
_cm->mark_loop(worker_id, _terminator, GENERATION, false /*not cancellable*/);
assert(_cm->task_queues()->is_empty(), "Should be empty");
}
@@ -255,49 +241,54 @@ void ShenandoahConcurrentMark::finish_mark() {
}
void ShenandoahConcurrentMark::finish_mark_work() {
- // Finally mark everything else we've got in our queues during the previous steps.
- // It does two different things for concurrent vs. mark-compact GC:
- // - For concurrent GC, it starts with empty task queues, drains the remaining
- // SATB buffers, and then completes the marking closure.
- // - For mark-compact GC, it starts out with the task queues seeded by initial
- // root scan, and completes the closure, thus marking through all live objects
- // The implementation is the same, so it's shared here.
ShenandoahHeap* const heap = ShenandoahHeap::heap();
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::finish_mark);
- uint nworkers = heap->workers()->active_workers();
- task_queues()->reserve(nworkers);
+ SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set();
- TaskTerminator terminator(nworkers, task_queues());
-
- switch (_generation->type()) {
- case YOUNG:{
- ShenandoahFinalMarkingTask task(this, &terminator);
- heap->workers()->run_task(&task);
- break;
- }
- case OLD:{
- ShenandoahFinalMarkingTask task(this, &terminator);
- heap->workers()->run_task(&task);
- break;
- }
- case GLOBAL:{
- ShenandoahFinalMarkingTask task(this, &terminator);
- heap->workers()->run_task(&task);
- break;
- }
- case NON_GEN:{
- ShenandoahFinalMarkingTask task(this, &terminator);
- heap->workers()->run_task(&task);
- break;
- }
- default:
- ShouldNotReachHere();
+ // First drain all remaining SATB buffers and put them to SATB MQ.
+ // Also, while we are iterating threads, mark the invisible roots.
+ {
+ ShenandoahTimingsTracker t(ShenandoahPhaseTimings::final_mark_flush_satb_roots);
+ ShenandoahInvisibleRootsMarkClosure invisible_cl;
+ ShenandoahFlushSATB flush_cl(satb_mq_set);
+ ShenandoahMultiThreadClosure mux(flush_cl, invisible_cl);
+ Threads::threads_do(&mux);
}
- if (!generation()->is_old() && heap->is_concurrent_young_mark_in_progress()) {
- // Lastly, ensure all the invisible roots are marked.
- ShenandoahInvisibleRootsMarkClosure cl;
- Threads::java_threads_do(&cl);
+
+ // There is a very high chance we have already completed the marking.
+ // But if there is outstanding work, finish it now.
+ if (!task_queues()->is_empty() || satb_mq_set.completed_buffers_num() > 0) {
+ ShenandoahGCPhase phase(ShenandoahPhaseTimings::finish_mark);
+
+ uint nworkers = heap->workers()->active_workers();
+ task_queues()->reserve(nworkers);
+ TaskTerminator terminator(nworkers, task_queues());
+
+ switch (_generation->type()) {
+ case YOUNG:{
+ ShenandoahFinalMarkingTask task(this, &terminator);
+ heap->workers()->run_task(&task);
+ break;
+ }
+ case OLD:{
+ ShenandoahFinalMarkingTask task(this, &terminator);
+ heap->workers()->run_task(&task);
+ break;
+ }
+ case GLOBAL:{
+ ShenandoahFinalMarkingTask task(this, &terminator);
+ heap->workers()->run_task(&task);
+ break;
+ }
+ case NON_GEN:{
+ ShenandoahFinalMarkingTask task(this, &terminator);
+ heap->workers()->run_task(&task);
+ break;
+ }
+ default:
+ ShouldNotReachHere();
+ }
}
assert(task_queues()->is_empty(), "Should be empty");
+ assert(satb_mq_set.completed_buffers_num() == 0, "Should be empty");
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp
index 9bbbada0be1..6563ffb6359 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp
@@ -1478,55 +1478,6 @@ HeapWord* ShenandoahFreeSet::try_allocate_from_mutator(ShenandoahAllocRequest& r
return nullptr;
}
-// This work method takes an argument corresponding to the number of bytes
-// free in a region, and returns the largest amount in heapwords that can be allocated
-// such that both of the following conditions are satisfied:
-//
-// 1. it is a multiple of card size
-// 2. any remaining shard may be filled with a filler object
-//
-// The idea is that the allocation starts and ends at card boundaries. Because
-// a region ('s end) is card-aligned, the remainder shard that must be filled is
-// at the start of the free space.
-//
-// This is merely a helper method to use for the purpose of such a calculation.
-size_t ShenandoahFreeSet::get_usable_free_words(size_t free_bytes) const {
- // e.g. card_size is 512, card_shift is 9, min_fill_size() is 8
- // free is 514
- // usable_free is 512, which is decreased to 0
- size_t usable_free = (free_bytes / CardTable::card_size()) << CardTable::card_shift();
- assert(usable_free <= free_bytes, "Sanity check");
- if ((free_bytes != usable_free) && (free_bytes - usable_free < ShenandoahHeap::min_fill_size() * HeapWordSize)) {
- // After aligning to card multiples, the remainder would be smaller than
- // the minimum filler object, so we'll need to take away another card's
- // worth to construct a filler object.
- if (usable_free >= CardTable::card_size()) {
- usable_free -= CardTable::card_size();
- } else {
- assert(usable_free == 0, "usable_free is a multiple of card_size and card_size > min_fill_size");
- }
- }
-
- return usable_free / HeapWordSize;
-}
-
-// Given a size argument, which is a multiple of card size, a request struct
-// for a PLAB, and an old region, return a pointer to the allocated space for
-// a PLAB which is card-aligned and where any remaining shard in the region
-// has been suitably filled by a filler object.
-// It is assumed (and assertion-checked) that such an allocation is always possible.
-HeapWord* ShenandoahFreeSet::allocate_aligned_plab(size_t size, ShenandoahAllocRequest& req, ShenandoahHeapRegion* r) {
- assert(_heap->mode()->is_generational(), "PLABs are only for generational mode");
- assert(r->is_old(), "All PLABs reside in old-gen");
- assert(!req.is_mutator_alloc(), "PLABs should not be allocated by mutators.");
- assert(is_aligned(size, CardTable::card_size_in_words()), "Align by design");
-
- HeapWord* result = r->allocate_aligned(size, req, CardTable::card_size());
- assert(result != nullptr, "Allocation cannot fail");
- assert(r->top() <= r->end(), "Allocation cannot span end of region");
- assert(is_aligned(result, CardTable::card_size_in_words()), "Align by design");
- return result;
-}
HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, ShenandoahAllocRequest& req, bool& in_new_region) {
assert (has_alloc_capacity(r), "Performance: should avoid full regions on this path: %zu", r->index());
@@ -1578,44 +1529,17 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah
// req.size() is in words, r->free() is in bytes.
if (req.is_lab_alloc()) {
size_t adjusted_size = req.size();
- size_t free = r->free(); // free represents bytes available within region r
- if (req.is_old()) {
- // This is a PLAB allocation(lab alloc in old gen)
- assert(_heap->mode()->is_generational(), "PLABs are only for generational mode");
- assert(_partitions.in_free_set(ShenandoahFreeSetPartitionId::OldCollector, r->index()),
- "PLABS must be allocated in old_collector_free regions");
-
- // Need to assure that plabs are aligned on multiple of card region
- // Convert free from unaligned bytes to aligned number of words
- size_t usable_free = get_usable_free_words(free);
- if (adjusted_size > usable_free) {
- adjusted_size = usable_free;
- }
- adjusted_size = align_down(adjusted_size, CardTable::card_size_in_words());
- if (adjusted_size >= req.min_size()) {
- result = allocate_aligned_plab(adjusted_size, req, r);
- assert(result != nullptr, "allocate must succeed");
- req.set_actual_size(adjusted_size);
- } else {
- // Otherwise, leave result == nullptr because the adjusted size is smaller than min size.
- log_trace(gc, free)("Failed to shrink PLAB request (%zu) in region %zu to %zu"
- " because min_size() is %zu", req.size(), r->index(), adjusted_size, req.min_size());
- }
+ size_t free = align_down(r->free() >> LogHeapWordSize, MinObjAlignment);
+ if (adjusted_size > free) {
+ adjusted_size = free;
+ }
+ if (adjusted_size >= req.min_size()) {
+ result = r->allocate(adjusted_size, req);
+ assert (result != nullptr, "Allocation must succeed: free %zu, actual %zu", free, adjusted_size);
+ req.set_actual_size(adjusted_size);
} else {
- // This is a GCLAB or a TLAB allocation
- // Convert free from unaligned bytes to aligned number of words
- free = align_down(free >> LogHeapWordSize, MinObjAlignment);
- if (adjusted_size > free) {
- adjusted_size = free;
- }
- if (adjusted_size >= req.min_size()) {
- result = r->allocate(adjusted_size, req);
- assert (result != nullptr, "Allocation must succeed: free %zu, actual %zu", free, adjusted_size);
- req.set_actual_size(adjusted_size);
- } else {
- log_trace(gc, free)("Failed to shrink TLAB or GCLAB request (%zu) in region %zu to %zu"
- " because min_size() is %zu", req.size(), r->index(), adjusted_size, req.min_size());
- }
+ log_trace(gc, free)("Failed to shrink LAB request (%zu) in region %zu to %zu"
+ " because min_size() is %zu", req.size(), r->index(), adjusted_size, req.min_size());
}
} else {
size_t size = req.size();
@@ -1821,8 +1745,8 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req, bo
for (idx_t i = beg; i <= end; i++) {
ShenandoahHeapRegion* r = _heap->get_region(i);
assert(i == beg || _heap->get_region(i - 1)->index() + 1 == r->index(), "Should be contiguous");
- assert(r->is_empty(), "Should be empty");
r->try_recycle_under_lock();
+ assert(r->is_empty(), "Should be empty");
r->set_affiliation(req.affiliation());
r->make_regular_allocation(req.affiliation());
if ((i == end) && (used_words_in_last_region > 0)) {
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp
index 083dd551aaa..7481b81c9c6 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp
@@ -453,7 +453,6 @@ private:
// locks will acquire them in the same order: first the global heap lock and then the rebuild lock.
ShenandoahRebuildLock _rebuild_lock;
- HeapWord* allocate_aligned_plab(size_t size, ShenandoahAllocRequest& req, ShenandoahHeapRegion* r);
size_t _total_humongous_waste;
@@ -639,7 +638,6 @@ private:
// Determine whether we prefer to allocate from left to right or from right to left within the OldCollector free-set.
void establish_old_collector_alloc_bias();
- size_t get_usable_free_words(size_t free_bytes) const;
void reduce_young_reserve(size_t adjusted_young_reserve, size_t requested_young_reserve);
void reduce_old_reserve(size_t adjusted_old_reserve, size_t requested_old_reserve);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
index b3a48f85114..f522a33a31c 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
@@ -53,8 +53,7 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() :
_requested_generation(nullptr),
_gc_mode(none),
_degen_point(ShenandoahGC::_degenerated_unset),
- _heap(ShenandoahGenerationalHeap::heap()),
- _age_period(0) {
+ _heap(ShenandoahGenerationalHeap::heap()) {
shenandoah_assert_generational();
set_name("ShenControl");
create_and_start();
@@ -230,15 +229,6 @@ void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const
}
}
-void ShenandoahGenerationalControlThread::maybe_set_aging_cycle() {
- if (_age_period-- == 0) {
- _heap->set_aging_cycle(true);
- _age_period = ShenandoahAgingCyclePeriod - 1;
- } else {
- _heap->set_aging_cycle(false);
- }
-}
-
void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest& request) {
log_debug(gc, thread)("Starting GC (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name());
@@ -269,7 +259,7 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest
// Cannot uncommit bitmap slices during concurrent reset
ShenandoahNoUncommitMark forbid_region_uncommit(_heap);
- // When a whitebox full GC is requested, set the tenuring threshold to zero
+ // When a whitebox full GC is requested, set the age census to always tenure
// so that all young objects are promoted to old. This ensures that tests
// using WB.fullGC() to promote objects to old gen will not loop forever.
ShenandoahTenuringOverride tenuring_override(request.cause == GCCause::_wb_full_gc,
@@ -534,9 +524,6 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen
// At this point:
// if (generation == YOUNG), this is a normal young cycle or a bootstrap cycle
// if (generation == GLOBAL), this is a GLOBAL cycle
- // In either case, we want to age old objects if this is an aging cycle
- maybe_set_aging_cycle();
-
ShenandoahGCSession session(cause, generation);
TraceCollectorStats tcs(_heap->monitoring_support()->concurrent_collection_counters());
@@ -615,7 +602,6 @@ bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(Shenandoah
void ShenandoahGenerationalControlThread::service_stw_full_cycle(GCCause::Cause cause) {
_heap->increment_total_collections(true);
ShenandoahGCSession session(cause, _heap->global_generation());
- maybe_set_aging_cycle();
ShenandoahFullGC gc;
gc.collect(cause);
_degen_point = ShenandoahGC::_degenerated_unset;
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
index 13e69d25268..5a3ab25eabe 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
@@ -80,9 +80,6 @@ private:
// A reference to the heap
ShenandoahGenerationalHeap* _heap;
- // This is used to keep track of whether to age objects during the current cycle.
- uint _age_period;
-
// This is true when the old generation cycle is in an interruptible phase (i.e., marking or
// preparing for mark).
ShenandoahSharedFlag _allow_old_preemption;
@@ -142,9 +139,6 @@ private:
void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation);
void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation);
- // Configure the heap to age objects and regions if the aging period has elapsed.
- void maybe_set_aging_cycle();
-
// Take the _control_lock and check for a request to run a gc cycle. If a request is found,
// the `prepare` methods are used to configure the heap and update heuristics accordingly.
void check_for_request(ShenandoahGCRequest& request);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
index 1b11c696d18..43dbddda9f7 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
@@ -312,11 +312,7 @@ void ShenandoahPrepareForGenerationalCompactionObjectClosure::do_object(oop p) {
// After full gc compaction, all regions have age 0. Embed the region's age into the object's age in order to preserve
// tenuring progress.
- if (_heap->is_aging_cycle()) {
- ShenandoahHeap::increase_object_age(p, from_region_age + 1);
- } else {
- ShenandoahHeap::increase_object_age(p, from_region_age);
- }
+ ShenandoahHeap::increase_object_age(p, from_region_age + 1);
if (_young_compact_point + obj_size > _young_to_region->end()) {
ShenandoahHeapRegion* new_to_region;
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
index 8c781f651d5..7170c88cd43 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
@@ -65,12 +65,11 @@ protected:
};
size_t ShenandoahGenerationalHeap::calculate_min_plab() {
- return align_up(PLAB::min_size(), CardTable::card_size_in_words());
+ return PLAB::min_size();
}
size_t ShenandoahGenerationalHeap::calculate_max_plab() {
- size_t MaxTLABSizeWords = ShenandoahHeapRegion::max_tlab_size_words();
- return align_down(MaxTLABSizeWords, CardTable::card_size_in_words());
+ return ShenandoahHeapRegion::max_tlab_size_words();
}
// Returns size in bytes
@@ -86,8 +85,6 @@ ShenandoahGenerationalHeap::ShenandoahGenerationalHeap(ShenandoahCollectorPolicy
_regulator_thread(nullptr),
_young_gen_memory_pool(nullptr),
_old_gen_memory_pool(nullptr) {
- assert(is_aligned(_min_plab_size, CardTable::card_size_in_words()), "min_plab_size must be aligned");
- assert(is_aligned(_max_plab_size, CardTable::card_size_in_words()), "max_plab_size must be aligned");
}
void ShenandoahGenerationalHeap::initialize_generations() {
@@ -350,7 +347,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint
oop copy_val = cast_to_oop(copy);
// Update the age of the evacuated object
- if (TO_GENERATION == YOUNG_GENERATION && is_aging_cycle()) {
+ if (TO_GENERATION == YOUNG_GENERATION) {
increase_object_age(copy_val, from_region_age + 1);
}
@@ -978,7 +975,7 @@ public:
// There have been allocations in this region since the start of the cycle.
// Any objects new to this region must not assimilate elevated age.
r->reset_age();
- } else if (ShenandoahGenerationalHeap::heap()->is_aging_cycle()) {
+ } else {
r->increment_age();
}
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
index d6893dc011e..91e5522de43 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
@@ -61,21 +61,10 @@ public:
private:
// ---------- Evacuations and Promotions
- //
- // True when regions and objects should be aged during the current cycle
- ShenandoahSharedFlag _is_aging_cycle;
// Age census used for adapting tenuring threshold
ShenandoahAgeCensus* _age_census;
public:
- void set_aging_cycle(bool cond) {
- _is_aging_cycle.set_cond(cond);
- }
-
- inline bool is_aging_cycle() const {
- return _is_aging_cycle.is_set();
- }
-
// Return the age census object for young gen
ShenandoahAgeCensus* age_census() const {
return _age_census;
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
index 91e92b500a3..c0df4bbe10c 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
@@ -1035,7 +1035,6 @@ HeapWord* ShenandoahHeap::allocate_memory_under_lock(ShenandoahAllocRequest& req
// memory.
HeapWord* result = _free_set->allocate(req, in_new_region);
- // Record the plab configuration for this result and register the object.
if (result != nullptr) {
if (req.is_mutator_alloc()) {
_alloc_rate.allocated((req.actual_size() + req.waste()) * HeapWordSize);
@@ -1044,33 +1043,10 @@ HeapWord* ShenandoahHeap::allocate_memory_under_lock(ShenandoahAllocRequest& req
if (req.is_old()) {
if (req.is_lab_alloc()) {
old_generation()->configure_plab_for_current_thread(req);
- } else {
- // Register the newly allocated object while we're holding the global lock since there's no synchronization
- // built in to the implementation of register_object(). There are potential races when multiple independent
- // threads are allocating objects, some of which might span the same card region. For example, consider
- // a card table's memory region within which three objects are being allocated by three different threads:
- //
- // objects being "concurrently" allocated:
- // [-----a------][-----b-----][--------------c------------------]
- // [---- card table memory range --------------]
- //
- // Before any objects are allocated, this card's memory range holds no objects. Note that allocation of object a
- // wants to set the starts-object, first-start, and last-start attributes of the preceding card region.
- // Allocation of object b wants to set the starts-object, first-start, and last-start attributes of this card region.
- // Allocation of object c also wants to set the starts-object, first-start, and last-start attributes of this
- // card region.
- //
- // The thread allocating b and the thread allocating c can "race" in various ways, resulting in confusion, such as
- // last-start representing object b while first-start represents object c. This is why we need to require all
- // register_object() invocations to be "mutually exclusive" with respect to each card's memory range.
- old_generation()->card_scan()->register_object(result);
-
- if (req.is_promotion()) {
- // Shared promotion.
- const size_t actual_size = req.actual_size() * HeapWordSize;
- log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size);
- old_generation()->expend_promoted(actual_size);
- }
+ } else if (req.is_promotion()) {
+ const size_t actual_size = req.actual_size() * HeapWordSize;
+ log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size);
+ old_generation()->expend_promoted(actual_size);
}
}
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
index ad8cd220968..33d5fa6b04f 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
@@ -66,7 +66,6 @@ class ShenandoahFreeSet;
class ShenandoahConcurrentMark;
class ShenandoahFullGC;
class ShenandoahMonitoringSupport;
-class ShenandoahPacer;
class ShenandoahReferenceProcessor;
class ShenandoahUncommitThread;
class ShenandoahVerifier;
@@ -534,7 +533,6 @@ private:
ShenandoahCollectorPolicy* _shenandoah_policy;
ShenandoahMode* _gc_mode;
ShenandoahFreeSet* _free_set;
- ShenandoahPacer* _pacer;
ShenandoahVerifier* _verifier;
ShenandoahPhaseTimings* _phase_timings;
@@ -559,7 +557,6 @@ public:
ShenandoahCollectorPolicy* shenandoah_policy() const { return _shenandoah_policy; }
ShenandoahMode* mode() const { return _gc_mode; }
ShenandoahFreeSet* free_set() const { return _free_set; }
- ShenandoahPacer* pacer() const { return _pacer; }
ShenandoahPhaseTimings* phase_timings() const { return _phase_timings; }
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp
index 7853238f080..9040a81848e 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp
@@ -386,12 +386,6 @@ public:
HeapWord* get_top_at_evac_start() const { return _top_at_evac_start; }
void record_top_at_evac_start() { _top_at_evac_start = _top; }
- // If next available memory is not aligned on address that is multiple of alignment, fill the empty space
- // so that returned object is aligned on an address that is a multiple of alignment_in_bytes. Requested
- // size is in words. It is assumed that this->is_old(). A pad object is allocated, filled, and registered
- // if necessary to assure the new allocation is properly aligned. Return nullptr if memory is not available.
- inline HeapWord* allocate_aligned(size_t word_size, ShenandoahAllocRequest &req, size_t alignment_in_bytes);
-
// Allocation (return nullptr if full)
inline HeapWord* allocate(size_t word_size, const ShenandoahAllocRequest& req);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp
index 39b7c732703..f004fdf0ea2 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp
@@ -33,59 +33,6 @@
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
-HeapWord* ShenandoahHeapRegion::allocate_aligned(size_t size, ShenandoahAllocRequest &req, size_t alignment_in_bytes) {
- shenandoah_assert_heaplocked_or_safepoint();
- assert(req.is_lab_alloc(), "allocate_aligned() only applies to LAB allocations");
- assert(is_object_aligned(size), "alloc size breaks alignment: %zu", size);
- assert(is_old(), "aligned allocations are only taken from OLD regions to support PLABs");
- assert(is_aligned(alignment_in_bytes, HeapWordSize), "Expect heap word alignment");
-
- HeapWord* orig_top = top();
- size_t alignment_in_words = alignment_in_bytes / HeapWordSize;
-
- // unalignment_words is the amount by which current top() exceeds the desired alignment point. We subtract this amount
- // from alignment_in_words to determine padding required to next alignment point.
-
- HeapWord* aligned_obj = (HeapWord*) align_up(orig_top, alignment_in_bytes);
- size_t pad_words = aligned_obj - orig_top;
- if ((pad_words > 0) && (pad_words < ShenandoahHeap::min_fill_size())) {
- pad_words += alignment_in_words;
- aligned_obj += alignment_in_words;
- }
-
- if (pointer_delta(end(), aligned_obj) < size) {
- // Shrink size to fit within available space and align it
- size = pointer_delta(end(), aligned_obj);
- size = align_down(size, alignment_in_words);
- }
-
- // Both originally requested size and adjusted size must be properly aligned
- assert (is_aligned(size, alignment_in_words), "Size must be multiple of alignment constraint");
- if (size >= req.min_size()) {
- // Even if req.min_size() may not be a multiple of card size, we know that size is.
- if (pad_words > 0) {
- assert(pad_words >= ShenandoahHeap::min_fill_size(), "pad_words expanded above to meet size constraint");
- ShenandoahHeap::fill_with_object(orig_top, pad_words);
- ShenandoahGenerationalHeap::heap()->old_generation()->card_scan()->register_object(orig_top);
- }
-
- make_regular_allocation(req.affiliation());
- adjust_alloc_metadata(req, size);
-
- HeapWord* new_top = aligned_obj + size;
- assert(new_top <= end(), "PLAB cannot span end of heap region");
- set_top(new_top);
- // We do not req.set_actual_size() here. The caller sets it.
- req.set_waste(pad_words);
- assert(is_object_aligned(new_top), "new top breaks alignment: " PTR_FORMAT, p2i(new_top));
- assert(is_aligned(aligned_obj, alignment_in_bytes), "obj is not aligned: " PTR_FORMAT, p2i(aligned_obj));
- return aligned_obj;
- } else {
- // The aligned size that fits in this region is smaller than min_size, so don't align top and don't allocate. Return failure.
- return nullptr;
- }
-}
-
HeapWord* ShenandoahHeapRegion::allocate_fill(size_t size) {
shenandoah_assert_heaplocked_or_safepoint();
assert(is_object_aligned(size), "alloc size breaks alignment: %zu", size);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp
index 5049113b665..f139f94fc8b 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp
@@ -22,7 +22,6 @@
*
*/
-#include "gc/shared/cardTable.hpp"
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
#include "gc/shenandoah/shenandoahGenerationalHeap.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
@@ -40,10 +39,10 @@ ShenandoahPLAB::ShenandoahPLAB() :
_promoted(0),
_promotion_failure_count(0),
_promotion_failure_words(0),
- _allows_promotion(false),
+ _allows_promotion(true),
_retries_enabled(false),
_heap(ShenandoahGenerationalHeap::heap()) {
- _plab = new PLAB(align_up(PLAB::min_size(), CardTable::card_size_in_words()));
+ _plab = new PLAB(PLAB::min_size());
}
ShenandoahPLAB::~ShenandoahPLAB() {
@@ -93,10 +92,8 @@ HeapWord* ShenandoahPLAB::allocate(size_t size, bool is_promotion) {
HeapWord* ShenandoahPLAB::allocate_slow(size_t size, bool is_promotion) {
assert(_heap->mode()->is_generational(), "PLABs only relevant to generational GC");
- // PLABs are aligned to card boundaries to avoid synchronization with concurrent
- // allocations in other PLABs.
const size_t plab_min_size = _heap->plab_min_size();
- const size_t min_size = (size > plab_min_size)? align_up(size, CardTable::card_size_in_words()): plab_min_size;
+ const size_t min_size = (size > plab_min_size) ? size : plab_min_size;
// Figure out size of new PLAB, using value determined at last refill.
size_t cur_size = _desired_size;
@@ -105,12 +102,7 @@ HeapWord* ShenandoahPLAB::allocate_slow(size_t size, bool is_promotion) {
}
// Expand aggressively, doubling at each refill in this epoch, ceiling at plab_max_size()
- // Doubling, starting at a card-multiple, should give us a card-multiple. (Ceiling and floor
- // are card multiples.)
const size_t future_size = MIN2(cur_size * 2, _heap->plab_max_size());
- assert(is_aligned(future_size, CardTable::card_size_in_words()), "Card multiple by construction, future_size: %zu"
- ", card_size: %u, cur_size: %zu, max: %zu",
- future_size, CardTable::card_size_in_words(), cur_size, _heap->plab_max_size());
// Record new heuristic value even if we take any shortcut. This captures
// the case when moderately-sized objects always take a shortcut. At some point,
@@ -128,8 +120,6 @@ HeapWord* ShenandoahPLAB::allocate_slow(size_t size, bool is_promotion) {
if (_plab->words_remaining() < plab_min_size) {
// Retire current PLAB. This takes care of any PLAB book-keeping.
- // retire_plab() registers the remnant filler object with the remembered set scanner without a lock.
- // Since PLABs are card-aligned, concurrent registrations in other PLABs don't interfere.
retire();
size_t actual_size = 0;
@@ -157,7 +147,6 @@ HeapWord* ShenandoahPLAB::allocate_slow(size_t size, bool is_promotion) {
Copy::fill_to_words(plab_buf + hdr_size, actual_size - hdr_size, badHeapWordVal);
#endif
}
- assert(is_aligned(actual_size, CardTable::card_size_in_words()), "Align by design");
_plab->set_buf(plab_buf, actual_size);
if (is_promotion && !_allows_promotion) {
return nullptr;
@@ -173,7 +162,6 @@ HeapWord* ShenandoahPLAB::allocate_slow(size_t size, bool is_promotion) {
}
HeapWord* ShenandoahPLAB::allocate_new_plab(size_t min_size, size_t word_size, size_t* actual_size) {
- assert(is_aligned(min_size, CardTable::card_size_in_words()), "Align by design");
assert(word_size >= min_size, "Requested PLAB is too small");
ShenandoahAllocRequest req = ShenandoahAllocRequest::for_plab(min_size, word_size);
@@ -183,7 +171,6 @@ HeapWord* ShenandoahPLAB::allocate_new_plab(size_t min_size, size_t word_size, s
} else {
*actual_size = 0;
}
- assert(is_aligned(res, CardTable::card_size_in_words()), "Align by design");
return res;
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp
index 385ab10893c..bc52d755139 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp
@@ -70,6 +70,7 @@ class outputStream;
SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_gross, "Pause Final Mark (G)") \
SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark, "Pause Final Mark (N)") \
SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_verify, " Verify") \
+ SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_flush_satb_roots, " Flush SATB and Roots") \
SHENANDOAH_WORKER_PHASE_DEF(f, finish_mark, " Finish Mark", \
" FM: ") \
SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_propagate_gc_state, " Propagate GC State") \
diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp
index 0ab97fdb51f..3647a818490 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp
@@ -500,9 +500,6 @@
"Turn on/off card-marking post-write barrier in Shenandoah: " \
" true when ShenandoahGCMode is generational, false otherwise") \
\
- product(bool, ShenandoahCASBarrier, true, DIAGNOSTIC, \
- "Turn on/off CAS barriers in Shenandoah") \
- \
product(bool, ShenandoahCloneBarrier, true, DIAGNOSTIC, \
"Turn on/off clone barriers in Shenandoah") \
\
@@ -532,10 +529,6 @@
"Allow young generation collections to suspend concurrent" \
" marking in the old generation.") \
\
- product(uintx, ShenandoahAgingCyclePeriod, 1, EXPERIMENTAL, \
- "With generational mode, increment the age of objects and" \
- "regions each time this many young-gen GC cycles are completed.") \
- \
develop(bool, ShenandoahEnableCardStats, false, \
"Enable statistics collection related to clean & dirty cards") \
\
diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp
index 1c2a4078904..d69475e62a3 100644
--- a/src/hotspot/share/gc/z/zRelocate.cpp
+++ b/src/hotspot/share/gc/z/zRelocate.cpp
@@ -642,7 +642,7 @@ private:
const zaddress to_addr = _forwarding->insert(from_addr, allocated_addr, &cursor);
if (to_addr != allocated_addr) {
// Already relocated, undo allocation
- _allocator->undo_alloc_object(to_page, to_addr, size);
+ _allocator->undo_alloc_object(to_page, allocated_addr, size);
increase_other_forwarded(size);
}
diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp
index 549d879de99..a41515edfbb 100644
--- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp
+++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
#include "jfr/dcmd/jfrDcmds.hpp"
#include "jfr/jfr.hpp"
#include "jfr/jni/jfrJavaSupport.hpp"
+#include "jfr/periodic/jfrRedactedEvents.hpp"
#include "jfr/recorder/jfrRecorder.hpp"
#include "jfr/recorder/service/jfrOptionSet.hpp"
#include "jfr/support/jfrThreadLocal.hpp"
@@ -401,11 +402,25 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out
};
void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
- outputStream* out = output();
+ print_help(output(), false);
+}
+
+static void print_filters(outputStream* out, JfrRedactedEvents::StringArray* filters) {
+ for (int i = 0; i < filters->length(); i++) {
+ out->print_cr(" %s", filters->at(i)->text());
+ }
+}
+
+void JfrConfigureFlightRecorderDCmd::print_help(outputStream* out, bool startup) {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
+ if (startup) {
+ out->print_cr("Syntax : -XX:FlightRecorderOptions:[options]");
+ out->print_cr("");
+ }
out->print_cr("Options:");
out->print_cr("");
- out->print_cr(" globalbuffercount (Optional) Number of global buffers. This option is a legacy");
+ out->print_cr( " %-19s (Optional) Number of global buffers. This option is a legacy",
+ startup ? "numglobalbuffers": "globalbuffercount");
out->print_cr(" option: change the memorysize parameter to alter the number of");
out->print_cr(" global buffers. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (STRING, default determined by the value for");
@@ -427,7 +442,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
out->print_cr(" gigabytes. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (STRING, 10M)");
out->print_cr("");
- out->print_cr(" repositorypath (Optional) Path to the location where recordings are stored until");
+ out->print_cr( " %-19s (Optional) Path to the location where recordings are stored until",
+ startup ? "repository" : "repositorypath");
out->print_cr(" they are written to a permanent file. (STRING, The default");
out->print_cr(" location is the temporary directory for the operating system. On");
out->print_cr(" Linux operating systems, the temporary directory is /tmp. On");
@@ -443,7 +459,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
out->print_cr(" degradation. This value cannot be changed once JFR has been");
out->print_cr(" initialized. (LONG, 64)");
out->print_cr("");
- out->print_cr(" thread_buffer_size (Optional) Local buffer size for each thread in bytes if one of");
+ out->print_cr( " %-19s (Optional) Local buffer size for each thread in bytes if one of",
+ startup ? "threadbuffersize" : "thread_buffer_size");
out->print_cr(" the following suffixes is not used: 'k' or 'K' for kilobytes or");
out->print_cr(" 'm' or 'M' for megabytes. Overriding this parameter could reduce");
out->print_cr(" performance and is not recommended. This value cannot be changed");
@@ -452,14 +469,77 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const {
out->print_cr(" preserve-repository (Optional) Preserve files stored in the disk repository after the");
out->print_cr(" Java Virtual Machine has exited. (BOOLEAN, false)");
out->print_cr("");
- out->print_cr("Options must be specified using the or = syntax.");
- out->print_cr("");
- out->print_cr("Example usage:");
- out->print_cr("");
- out->print_cr(" $ jcmd JFR.configure");
- out->print_cr(" $ jcmd JFR.configure repositorypath=/temporary");
- out->print_cr(" $ jcmd JFR.configure stackdepth=256");
- out->print_cr(" $ jcmd JFR.configure memorysize=100M");
+ if (startup) {
+ out->print_cr(" old-object-queue-size (Optional) Maximum number of old objects to track. By default,");
+ out->print_cr(" the number of objects is set to 256. (LONG, 256)");
+ out->print_cr("");
+ out->print_cr(" redact-argument (Optional) Replace command-line arguments that match a");
+ out->print_cr(" semicolon-separated list of glob patterns, for example,");
+ out->print_cr(" *secret*;password*. Matching is case-insensitive, and the");
+ out->print_cr(" supported wildcards are '*' and '?'. To redact multiple arguments,");
+ out->print_cr(" use a literal space (' ') as a separator. For example, to match");
+ out->print_cr(" the two arguments --auth username:token, use the filter");
+ out->print_cr(" --auth *:*. Filters containing spaces must be quoted as a single");
+ out->print_cr(" command-line argument, for example,");
+ out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=--auth *:*'.");
+ out->print_cr(" Arguments containing spaces might not be matched as expected.");
+ out->print_cr(" The option redact-argument is best-effort and applies only to");
+ out->print_cr(" command-line arguments in the jdk.JVMInformation event and to");
+ out->print_cr(" the java.command system property in the jdk.InitialSystemProperty");
+ out->print_cr(" event. Other events, such as jdk.ProcessStart (child processes),");
+ out->print_cr(" are not redacted.");
+ out->print_cr("");
+ out->print_cr(" If the redact-argument option is not specified, the following");
+ out->print_cr(" filters are used by default:");
+ out->print_cr("");
+ print_filters(out, JfrRedactedEvents::argument_filters());
+ out->print_cr("");
+ out->print_cr(" To load patterns from a file (one per line), use @.");
+ out->print_cr(" To add to the default patterns instead of replacing them, prefix");
+ out->print_cr(" the whole list with '+', for example, +*foo*;@redact.txt.");
+ out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for");
+ out->print_cr(" command-line arguments. Redacted arguments will be replaced");
+ out->print_cr(" with '[REDACTED]'. (STRING, default filters)");
+ out->print_cr("");
+ out->print_cr(" redact-key (Optional) Replace the value of environment variables and system");
+ out->print_cr(" properties whose key matches a semicolon-separated list of glob");
+ out->print_cr(" patterns, for example, *password*;*token*. Matching is");
+ out->print_cr(" case-insensitive, and the supported wildcards are '*' and '?'.");
+ out->print_cr(" The option redact-key is best-effort and applies only to the");
+ out->print_cr(" jdk.InitialSystemProperty, jdk.InitialEnvironmentVariable and");
+ out->print_cr(" jdk.JVMInformation (-Dkey...) events. Other events, such as");
+ out->print_cr(" jdk.InitialSecurityProperty, are not redacted.");
+ out->print_cr("");
+ out->print_cr(" If the redact-key option is not specified, the");
+ out->print_cr(" following filters are used by default:");
+ out->print_cr("");
+ print_filters(out, JfrRedactedEvents::key_filters());
+ out->print_cr("");
+ out->print_cr(" To load patterns from a file (one per line), use @.");
+ out->print_cr(" To add to the default patterns instead of replacing them, prefix");
+ out->print_cr(" the whole list with '+', for example, +*cred*;@keys.txt.");
+ out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for key");
+ out->print_cr(" matching. Redacted values will be replaced with '[REDACTED]'.");
+ out->print_cr(" (STRING, default filters)");
+ out->print_cr("");
+ out->print_cr("Options must be specified using the = syntax. Multiple options are separated");
+ out->print_cr("with a comma.");
+ out->print_cr("");
+ out->print_cr("Example usage:");
+ out->print_cr("");
+ out->print_cr(" -XX:FlightRecorderOptions:repository=/temporary,stackdepth=256");
+ out->print_cr(" -XX:FlightRecorderOptions:'redact-key=+*confidential*;*private*,redact-argument=+https://*:*@*'");
+ out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=+-*private *'");
+ } else {
+ out->print_cr("Options must be specified using the or = syntax.");
+ out->print_cr("");
+ out->print_cr("Example usage:");
+ out->print_cr("");
+ out->print_cr(" $ jcmd JFR.configure");
+ out->print_cr(" $ jcmd JFR.configure repositorypath=/temporary");
+ out->print_cr(" $ jcmd JFR.configure stackdepth=256");
+ out->print_cr(" $ jcmd JFR.configure memorysize=100M");
+ }
out->print_cr("");
}
diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp
index 8e7dad5a20c..aecd187a2e0 100644
--- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp
+++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -202,6 +202,7 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser {
return "Low";
}
static int num_arguments() { return 10; }
+ static void print_help(outputStream* out, bool startup);
virtual void execute(DCmdSource source, TRAPS);
virtual void print_help(const char* name) const;
};
diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml
index 09d9e0ccabf..22bd21c8ff4 100644
--- a/src/hotspot/share/jfr/metadata/metadata.xml
+++ b/src/hotspot/share/jfr/metadata/metadata.xml
@@ -1,7 +1,7 @@
go to same region
+// or uncommon trap
+//
+// 1. In some cases, we can prove that succ cannot be reached,
+// and we can fold away the iff2. Example:
+//
+// if (n < -1 && n > 1) { succ } else { fail }
+// // 1st condition: n in [min_int .. -2]
+// // 2nd condition: n in [2 .. max_int]
+// // -> no overlap -> constant fold iff2 towards fail2
+// //
+// // Equivalent, if we flip everything:
+// if (n >= -1 || n <= 1) { fail } else { succ }
+//
+// 2. In other cases, we can replace the two CmpI with
+// a single CmpU. We fold iff1 towards middle, and
+// replace the iff2 condition with the CmpU. Example:
+//
+// if (n >= 0 && n < 10) { succ } else { fail }
+// // transformed to:
+// if (n = arr.length) { throw ArrayOutOfBoundsException }
+// // transformed to:
+// if (n >=u arr.length) { throw ArrayOutOfBoundsException }
+//
+// Note1: we assume that the CmpI nodes are canonicalized to the
+// point where n is always on the lhs. This is a limitation,
+// but as long as v1 and v2 are constants they will eventually
+// be canonicalized to the rhs. For variables, this may not always
+// happen.
+//
+// Note2: We are flexible about the IfProj nodes: middle and succ
+// could both be either IfTrue or IfFalse.
+//
+// Note3: Surrounding code has a different naming scheme!
+// In has_only_uncommon_traps, the path towards the
+// uncommon trap (e.g. failed range check) is called
+// "success", while the path that does not go to
+// the uncommon trap (e.g. in-bounds access) is called
+// "fail". I think that is counter-intuitive, so I now
+// used a different naming scheme here.
+//
+// Return true iff we could perform one of the optimizations.
+bool IfNode::fold_compares_helper(IfProjNode* middle, IfProjNode* fail2, IfProjNode* succ, PhaseIterGVN* igvn) {
+ assert(fail2->in(0) == this, "link iff2->fail2");
+ assert(succ->in(0) == this, "link iff2->succ");
- const TypeInt* lo_type = IfNode::filtered_int_type(igvn, n, otherproj);
- const TypeInt* hi_type = IfNode::filtered_int_type(igvn, n, success);
+ IfNode* iff1 = middle->in(0)->as_If();
+ IfNode* iff2 = this;
+ BoolNode* bool1 = iff1->in(1)->as_Bool();
+ BoolNode* bool2 = iff2->in(1)->as_Bool();
+ CmpNode* cmp1 = bool1->in(1)->as_Cmp();
+ CmpNode* cmp2 = bool2->in(1)->as_Cmp();
+ assert(cmp1->Opcode() == Op_CmpI, "comparisons must be CmpI");
+ assert(cmp2->Opcode() == Op_CmpI, "comparisons must be CmpI");
- BoolTest::mask lo_test = dom_bool->_test._test;
- BoolTest::mask hi_test = this_bool->_test._test;
- BoolTest::mask cond = hi_test;
+ IfProjNode* fail1 = middle->other_if_proj();
- PhaseTransform::SpeculativeProgressGuard progress_guard(igvn);
- // convert:
- //
- // dom_bool = x {<,<=,>,>=} a
- // / \
- // proj = {True,False} / \ otherproj = {False,True}
- // /
- // this_bool = x {<,<=} b
- // / \
- // fail = {True,False} / \ success = {False,True}
- // /
- //
- // (Second test guaranteed canonicalized, first one may not have
- // been canonicalized yet)
- //
- // into:
- //
- // cond = (x - lo) {u,>=u} adjusted_lim
- // / \
- // fail / \ success
- // /
- //
+ Node* v1 = cmp1->in(2);
+ Node* v2 = cmp2->in(2);
+ Node* n = cmp1->in(1);
+ assert(cmp2->in(1) == n, "n must be lhs in both CmpI");
- // Figure out which of the two tests sets the upper bound and which
- // sets the lower bound if any.
- Node* adjusted_lim = nullptr;
- if (lo_type != nullptr && hi_type != nullptr && hi_type->_lo > lo_type->_hi &&
- hi_type->_hi == max_jint && lo_type->_lo == min_jint && lo_test != BoolTest::ne) {
- assert((dom_bool->_test.is_less() && !proj->_con) ||
- (dom_bool->_test.is_greater() && proj->_con), "incorrect test");
-
- // this_bool = <
- // dom_bool = >= (proj = True) or dom_bool = < (proj = False)
- // x in [a, b[ on the fail (= True) projection, b > a-1 (because of hi_type->_lo > lo_type->_hi test above):
- // lo = a, hi = b, adjusted_lim = b-a, cond = (proj = True) or dom_bool = <= (proj = False)
- // x in ]a, b[ on the fail (= True) projection, b > a:
- // lo = a+1, hi = b, adjusted_lim = b-a-1, cond = = (proj = True) or dom_bool = < (proj = False)
- // x in [a, b] on the fail (= True) projection, b+1 > a-1:
- // lo = a, hi = b, adjusted_lim = b-a+1, cond = (proj = True) or dom_bool = <= (proj = False)
- // x in ]a, b] on the fail (= True) projection b+1 > a:
- // lo = a+1, hi = b, adjusted_lim = b-a, cond = transform(new AddINode(lo, igvn->intcon(1)));
+ // Optimization 1: try to prove that succ is not reachable.
+ // Which values of n can pass iff1 to middle AND iff2 to succ?
+ const TypeInt* type_middle = filtered_int_type(igvn, n, middle);
+ if (type_middle != nullptr) {
+ const TypeInt* type_succ = filtered_int_type(igvn, n, succ);
+ if (type_succ != nullptr) {
+ if (type_middle->filter(type_succ) == Type::TOP) {
+ // The intersection is empty -> succ is not reachable.
+ // Fold iff2 towards fail2 (and away from succ).
+ igvn->replace_input_of(iff2, 1, igvn->intcon(fail2->_con));
+ return true; // success: succ not reachable
}
- } else if (hi_test == BoolTest::le) {
- if (lo_test == BoolTest::ge || lo_test == BoolTest::lt) {
- adjusted_lim = igvn->transform(new SubINode(hi, lo));
- adjusted_lim = igvn->transform(new AddINode(adjusted_lim, igvn->intcon(1)));
- cond = BoolTest::lt;
- } else if (lo_test == BoolTest::gt || lo_test == BoolTest::le) {
- adjusted_lim = igvn->transform(new SubINode(hi, lo));
- lo = igvn->transform(new AddINode(lo, igvn->intcon(1)));
- cond = BoolTest::lt;
- } else {
- assert(false, "unhandled lo_test: %d", lo_test);
- return false;
- }
- } else {
- assert(igvn->_worklist.member(in(1)) && in(1)->Value(igvn) != igvn->type(in(1)), "unhandled hi_test: %d", hi_test);
- return false;
}
- // this test was canonicalized
- assert(this_bool->_test.is_less() && fail->_con, "incorrect test");
- } else if (lo_type != nullptr && hi_type != nullptr && lo_type->_lo > hi_type->_hi &&
- lo_type->_hi == max_jint && hi_type->_lo == min_jint && lo_test != BoolTest::ne) {
+ }
- // this_bool = <
- // dom_bool = < (proj = True) or dom_bool = >= (proj = False)
- // x in [b, a[ on the fail (= False) projection, a > b-1 (because of lo_type->_lo > hi_type->_hi above):
- // lo = b, hi = a, adjusted_lim = a-b, cond = >=u
- // dom_bool = <= (proj = True) or dom_bool = > (proj = False)
- // x in [b, a] on the fail (= False) projection, a+1 > b-1:
- // lo = b, hi = a, adjusted_lim = a-b+1, cond = >=u
- // lo = b, hi = a, adjusted_lim = a-b, cond = >u doesn't work because a = b - 1 is possible, then b-a = -1
- // this_bool = <=
- // dom_bool = < (proj = True) or dom_bool = >= (proj = False)
- // x in ]b, a[ on the fail (= False) projection, a > b:
- // lo = b+1, hi = a, adjusted_lim = a-b-1, cond = >=u
- // dom_bool = <= (proj = True) or dom_bool = > (proj = False)
- // x in ]b, a] on the fail (= False) projection, a+1 > b:
- // lo = b+1, hi = a, adjusted_lim = a-b, cond = >=u
- // lo = b+1, hi = a, adjusted_lim = a-b-1, cond = >u doesn't work because a = b is possible, then b-a-1 = -1
+ // Optimization 2: try to replace the two CmpI with one CmpU
+ // We can handle the following 4 cases:
+ // Input: two CmpI Output: one CmpU Assumption
+ // -------------------- ------------------------- -------------------
+ // a) (n > lo && n < hi) -> n - lo - 1 2 && n < 5 ) n - 3 lo && n <= hi) -> n - lo - 1 2 && n <= 5 ) n - 3 = lo && n < hi) -> n - lo = 2 && n < 5 ) n - 2 = lo && n <= hi) -> n - lo <=u hi - lo (assuming lo <= hi)
+ // (n >= 2 && n <= 5 ) n - 2 <=u 3
+ // range: [2, 3, 4, 5]
+ //
+ // Note1: the rhs of the CmpU indicates the cardinality of the range,
+ // allowing n to have exactly that many different values.
+ //
+ // Note2: all 4 case have an assumption: lo must be sufficiently smaller
+ // than hi. Below, and with the use of Lemma1 from below, we will
+ // prove that this implies that the rhs of the CmpU never
+ // underflows or overflows, which is critical for correctness.
+ //
+ // Below, we will prove and implement each of these cases. But first,
+ // we must handle the combinations of IfTrue/IfFalse projections for
+ // middle and succ, and extract which one is the lower bound (lo) and
+ // which one the upper bound (hi).
+ //
+ // <---- lower bound -----> <----------- succ -------------> <---- upper bound ----->
+ // [min_int .. lo_type->hi] [lo_type->hi+1 .. hi_type->lo-1] [hi_type->lo .. max_int]
+ // ^ ^
+ // n {>/>=} lo n {<=} hi
+ //
+ // The trick is then to "shift down" the succ range, to create only
+ // a single transition point.
+ //
+ // <----------- succ -------------> <------------ unsigned upper bound ------------->
+ // [0 .. ] [ .. max_uint]
+ // ^
+ // CmpU
- swap(lo, hi);
- swap(lo_type, hi_type);
- swap(lo_test, hi_test);
+ BoolTest::mask test1 = bool1->_test._test;
+ BoolTest::mask test2 = bool2->_test._test;
+ if (middle->Opcode() == Op_IfFalse) { test1 = BoolTest::negate_mask(test1); }
+ if (succ->Opcode() == Op_IfFalse) { test2 = BoolTest::negate_mask(test2); }
- assert((dom_bool->_test.is_less() && proj->_con) ||
- (dom_bool->_test.is_greater() && !proj->_con), "incorrect test");
-
- cond = (hi_test == BoolTest::le || hi_test == BoolTest::gt) ? BoolTest::gt : BoolTest::ge;
-
- if (lo_test == BoolTest::lt) {
- if (hi_test == BoolTest::lt || hi_test == BoolTest::ge) {
- cond = BoolTest::ge;
- } else if (hi_test == BoolTest::le || hi_test == BoolTest::gt) {
- adjusted_lim = igvn->transform(new SubINode(hi, lo));
- adjusted_lim = igvn->transform(new AddINode(adjusted_lim, igvn->intcon(1)));
- cond = BoolTest::ge;
- } else {
- assert(false, "unhandled hi_test: %d", hi_test);
- return false;
- }
- } else if (lo_test == BoolTest::le) {
- if (hi_test == BoolTest::lt || hi_test == BoolTest::ge) {
- lo = igvn->transform(new AddINode(lo, igvn->intcon(1)));
- cond = BoolTest::ge;
- } else if (hi_test == BoolTest::le || hi_test == BoolTest::gt) {
- adjusted_lim = igvn->transform(new SubINode(hi, lo));
- lo = igvn->transform(new AddINode(lo, igvn->intcon(1)));
- cond = BoolTest::ge;
- } else {
- assert(false, "unhandled hi_test: %d", hi_test);
- return false;
- }
- } else {
- assert(igvn->_worklist.member(in(1)) && in(1)->Value(igvn) != igvn->type(in(1)), "unhandled lo_test: %d", lo_test);
- return false;
- }
- // this test was canonicalized
- assert(this_bool->_test.is_less() && !fail->_con, "incorrect test");
+ Node* lo = nullptr;
+ Node* hi = nullptr;
+ const TypeInt* lo_type = nullptr;
+ const TypeInt* hi_type = nullptr;
+ BoolTest::mask lo_test = BoolTest::illegal;
+ BoolTest::mask hi_test = BoolTest::illegal;
+ if (BoolTest::is_greater(test1) && BoolTest::is_less(test2)) {
+ lo = v1;
+ hi = v2;
+ lo_type = IfNode::filtered_int_type(igvn, n, fail1);
+ hi_type = IfNode::filtered_int_type(igvn, n, fail2);
+ lo_test = test1;
+ hi_test = test2;
+ } else if (BoolTest::is_less(test1) && BoolTest::is_greater(test2)) {
+ lo = v2;
+ hi = v1;
+ lo_type = IfNode::filtered_int_type(igvn, n, fail2);
+ hi_type = IfNode::filtered_int_type(igvn, n, fail1);
+ lo_test = test2;
+ hi_test = test1;
} else {
- const TypeInt* failtype = filtered_int_type(igvn, n, proj);
- if (failtype != nullptr) {
- const TypeInt* type2 = filtered_int_type(igvn, n, fail);
- if (type2 != nullptr) {
- if (failtype->filter(type2) == Type::TOP) {
- // previous if determines the result of this if so
- // replace Bool with constant
- igvn->replace_input_of(this, 1, igvn->intcon(success->_con));
- progress_guard.commit();
- return true;
- }
- }
- }
+ // Could not find upper and lower bound.
+ return false;
+ }
+ assert(BoolTest::is_greater(lo_test), "lower bound: n {>/>=} lo");
+ assert(BoolTest::is_less(hi_test), "upper bound: n {<=} lo");
+
+ // Check that we got lower and upper bounds as expected.
+ if (lo_type == nullptr ||
+ hi_type == nullptr ||
+ hi_type->_hi != max_jint ||
+ lo_type->_lo != min_jint) {
+ // Upper and lower bounds could not be established.
return false;
}
- assert(lo != nullptr && hi != nullptr, "sanity");
- Node* hook = new Node(lo); // Add a use to lo to prevent him from dying
- // Merge the two compares into a single unsigned compare by building (CmpU (n - lo) (hi - lo))
- Node* adjusted_val = igvn->transform(new SubINode(n, lo));
- if (adjusted_lim == nullptr) {
- adjusted_lim = igvn->transform(new SubINode(hi, lo));
- }
- hook->destruct(igvn);
+ // -------------------------------------------------------------------
+ // In the proofs below, we need some basic Lemmas to deal with integer
+ // signed and unsigned arithmetic.
+ //
+ // Lemma1:
+ // Let a and b be in [min_int .. max_int].
+ // If a >=s b, then:
+ // U(a - b) = a - b
+ //
+ // Proof:
+ // a >= b
+ // -> a - b >= 0
+ //
+ // a <= max_int
+ // b >= min_int
+ // -> a - b <= max_int - min_int = 2^32-1
+ //
+ // 0 <= a - b <= 2^32-1
+ // -> cast to unsigned has no overflow
+ // -> U(a - b) = a - b
+ //
+ // Lemma2:
+ // Let a and b be in [min_int .. max_int].
+ // If a a - b < 0
+ //
+ // a >= min_int
+ // b <= max_int
+ // -> a - b >= min_int - max_int = 2^32-1
+ //
+ // 2^32-1 <= a - b < 0
+ // -> cast to unsigned leads to exactly one overflow
+ // -> U(a - b) = a - b + 2^32
+ //
+ // Lemma3:
+ // Let a and b be in [min_int .. max_int].
+ // a + 2^32 > b
+ //
+ // Proof:
+ // Using a >= min_int, and b <= max_int:
+ // a + 2^32 >= min_int + 2^32
+ // = max_int + 1
+ // >= b + 1
+ // > b
+ // -------------------------------------------------------------------
- if (adjusted_val->is_top() || adjusted_lim->is_top()) {
- return false;
+ // Handle the 4 cases.
+ // All produce this form: n - lo + x1 hi - lo + x2
+ Node* x1 = nullptr;
+ Node* x2 = nullptr;
+ BoolTest::mask cond = BoolTest::illegal;
+ if (lo_test == BoolTest::gt && hi_test == BoolTest::lt) {
+ // We perform the the (CHECK) below, which implies (LO-HI),
+ // as we will show below.
+ if (lo_type->_hi >= hi_type->_lo) {
+ return false; // (CHECK) fails, we cannot establish (LO-HI) assumption.
+ }
+ // a) (n > lo && n < hi) -> n - lo - 1 _hi] for n <= lo
+ // -> lo_type->_hi = lo->_hi
+ // hi_type = [hi->_lo .. max_int] for n >= lo
+ // -> hi_type->_lo = hi->_lo
+ // We will need the assumption (LO-HI) below, which we can
+ // establish with the following (CHECK):
+ // lo_type->_hi < hi_type->_lo (CHECK)
+ // -> lo->_hi < hi->_lo
+ // -> lo < hi (LO-HI)
+ //
+ // Case n <= lo:
+ // (BEFORE) is always false, show (AFTER) is always false.
+ // Since lo < hi (LO-HI), S(lo+1) = lo+1 (no overflow):
+ // -> lo+1 <= hi
+ // -> n < lo+1
+ // U(n - (lo + 1)) < U(hi - (lo + 1))
+ // -- Lemma2 (n < lo+1) -- -- Lemma1 (lo+1 <= hi) --
+ // n - (lo + 1) + 2^32 < hi - (lo + 1)
+ // n + 2^32 < hi
+ // Always false by Lemma3.
+ //
+ // Case lo < n < hi:
+ // (BEFORE) is always true, show (AFTER) is always true.
+ // Since lo < hi (LO-HI), S(lo+1) = lo+1 (no overflow):
+ // -> lo+1 <= hi
+ // -> n >= lo+1
+ // U(n - (lo + 1)) < U(hi - (lo + 1))
+ // -- Lemma1 (n >= lo+1) -- -- Lemma1 (lo+1 <= hi) --
+ // n - (lo + 1) < hi - (lo + 1)
+ // n < hi
+ // Corresponds to case assumption, so always true.
+ //
+ // Case n >= hi:
+ // (BEFORE) is always false, show (AFTER) is always false.
+ // Since lo < hi (LO-HI), S(lo+1) = lo+1 (no overflow):
+ // -> lo+1 <= hi
+ // U(n - (lo + 1)) < U(hi - (lo + 1))
+ // -- Lemma1 (n >= lo+1) -- -- Lemma1 (lo+1 <= hi) --
+ // n - (lo + 1) < hi - (lo + 1)
+ // n < hi
+ // Contradicts case assumption, so always false.
+ // QED.
+ //
+ // Note: we cannot use anything more relaxed than the assumption
+ // lo < hi: with lo=hi the rhs of the CmpU would underflow.
+ //
+ // Produce form: n - lo + x1 hi - lo + x2
+ // n - lo - 1 intcon(-1);
+ x2 = igvn->intcon(-1);
+ cond = BoolTest::lt;
+ } else if (lo_test == BoolTest::gt && hi_test == BoolTest::le) {
+ // We perform the the (CHECK) below, which implies (LO-HI),
+ // as we will show below.
+ if (lo_type->_hi >= hi_type->_lo) {
+ return false; // (CHECK) fails, we cannot establish (LO-HI) assumption.
+ }
+ // b) (n > lo && n <= hi) -> n - lo - 1 _hi] for n <= lo
+ // -> lo_type->_hi = lo->_hi
+ // hi_type = [min(hi->_lo+1, max_int) .. max_int] for n > hi
+ // -> hi_type->_lo <= lo->_lo + 1
+ // We will need the assumption (LO-HI) below, which we can
+ // establish with the following (CHECK):
+ // lo_type->_hi < hi_type->_lo (CHECK)
+ // -> lo->_hi < hi->_lo + 1
+ // -> lo < hi + 1
+ // -> lo <= hi (LO-HI)
+ //
+ // Case A: lo = hi
+ // Let y = lo = hi
+ // -> n > lo && n <= hi vs n - lo - 1 n > y && n <= y vs n - y - 1 n < lo+1
+ // U(n - (lo + 1)) < U(hi - lo)
+ // -- Lemma2 (n < lo+1) -- -- Lemma1 (lo <= hi, LO-HI) --
+ // n - (lo + 1) + 2^32 < hi - lo
+ // n - 1 + 2^32 < hi
+ // n + 2^32 <= hi
+ // Always false by Lemma3.
+ // Note: To apply Lemma2 above, we must use (Case B), we
+ // could not have done it with (LO-HI) alone.
+ //
+ // Case lo < n <= hi:
+ // (BEFORE) is always true, show (AFTER) is always true.
+ // Since lo < hi (Case B), S(lo+1) = lo+1 (no overflow):
+ // -> n >= lo+1
+ // U(n - (lo + 1)) < U(hi - lo)
+ // -- Lemma1 (n >= lo+1) -- -- Lemma1 (lo <= hi, LO-HI) --
+ // n - (lo + 1) < hi - lo
+ // n - 1 < hi
+ // n <= hi
+ // Follows from case assumption, so always true.
+ //
+ // Case n > hi:
+ // (BEFORE) is always false, show (AFTER) is always false.
+ // Since lo < hi (Case B), S(lo+1) = lo+1 (no overflow):
+ // -> lo+1 <= hi
+ // -> n > lo+1
+ // U(n - (lo + 1)) < U(hi - lo)
+ // -- Lemma1 (n > lo+1) -- -- Lemma1 (lo <= hi, LO-HI) --
+ // n - (lo + 1) < hi - lo
+ // n - 1 < hi
+ // n <= hi
+ // Contradicts case assumption, so always false.
+ // QED.
+ //
+ // Note: we cannot use anything more relaxed than the assumption
+ // lo <= hi: with lo=hi+1 the rhs of the CmpU would underflow.
+ //
+ // Produce form: n - lo + x1 hi - lo + x2
+ // n - lo - 1 intcon(-1);
+ x2 = igvn->intcon(0);
+ cond = BoolTest::lt;
+ } else if (lo_test == BoolTest::ge && hi_test == BoolTest::lt) {
+ // We perform the the (CHECK) below, which implies (LO-HI),
+ // as we will show below.
+ if (lo_type->_hi >= hi_type->_lo) {
+ return false; // (CHECK) fails, we cannot establish (LO-HI) assumption.
+ }
+ // c) (n >= lo && n < hi) -> n - lo _hi - 1)] for n < lo
+ // -> lo_type->_hi >= lo->_hi - 1
+ // hi_type = [b->_lo .. max_int] for n >= hi
+ // -> hi_type->_lo = hi->_lo
+ // We will need the assumption (LO-HI) below, which we can
+ // establish with the following (CHECK):
+ // lo_type->_hi < hi_type->_lo
+ // -> lo->_hi - 1 < hi->_lo
+ // -> lo->_hi <= hi->_lo
+ // -> lo <= hi (HI-LO)
+ //
+ // Case n < lo:
+ // (BEFORE) is always false, show (AFTER) is always false.
+ // U(n - lo) < U(hi - lo)
+ // -- Lemma2 (n < lo) -- -- Lemma1 (lo <= hi, LO-HI) --
+ // n - lo + 2^32 < hi - lo
+ // n + 2^32 < hi
+ // Always false by Lemma3.
+ //
+ // Case lo <=s n = lo) -- -- Lemma1 (lo <= hi, LO-HI) --
+ // n - lo < hi - lo
+ // n < hi
+ // Follows from case assumption, so always true.
+ //
+ // Case n >=s hi:
+ // (BEFORE) is always false, show (AFTER) is always false.
+ // U(n - lo) < U(hi - lo)
+ // -- Lemma1 (n >= lo) -- -- Lemma1 (lo <= hi, LO-HI) --
+ // n - lo < hi - lo
+ // n < hi
+ // Contradicts case assumption, so always false.
+ // QED.
+ //
+ /// Note: we cannot use anything more relaxed than the assumption
+ // lo <= hi: with lo=hi+1 the rhs of the CmpU would underflow.
+ //
+ // Produce form: n - lo + x1 hi - lo + x2
+ // n - lo intcon(0);
+ x2 = igvn->intcon(0);
+ cond = BoolTest::lt;
+ } else {
+ assert (lo_test == BoolTest::ge && hi_test == BoolTest::le, "");
+ // We perform the the (CHECK) below, which implies (LO-HI),
+ // as we will show below.
+ jlong lo_type_hi = lo_type->_hi;
+ jlong hi_type_lo = hi_type->_lo;
+ if (lo_type_hi >= hi_type_lo - 1) {
+ return false; // (CHECK) fails, we cannot establish (LO-HI) assumption.
+ }
+ // d) (n >= lo && n <= hi) -> n - lo <=u hi - lo (assuming lo <= hi)
+ // (BEFORE) (AFTER) (LO-HI)
+ //
+ // Proof:
+ // From IfNode::filtered_int_type, we get:
+ // lo_type = [min_int .. max(min_int, lo->_hi-1)] for n < lo
+ // -> lo_type->_hi >= lo->_hi - 1
+ // hi_type = [min(hi->_lo+1, max_int) .. max_int] for n > hi
+ // -> hi_type->_lo <= hi->_lo + 1
+ // We will need the assumption (LO-HI) below, which we can
+ // establish with the following (CHECK), which we must compute in
+ // long to avoid underflow:
+ // lo_type->_hi < hi_type->_lo - 1 (CHECK)
+ // -> lo_type->_hi + 1 <= hi_type->_lo - 1
+ // -> lo->_hi <= hi->_lo
+ // -> lo <= hi (LO-HI)
+ //
+ // Case n = lo, LO-HI) --
+ // n - lo + 2^32 <= hi - lo
+ // n + 2^32 <= hi
+ // Always false by Lemma3.
+ //
+ // Case lo <=s n <=s hi:
+ // (BEFORE) is always true, show (AFTER) is always true.
+ // U(n - lo) <= U(hi - lo)
+ // -- Lemma1 (n >= lo) -- -- Lemma1 (hi >= lo, LO-HI) --
+ // n - lo <= hi - lo
+ // n <= hi
+ // Corresponds to case assumption, so always true.
+ //
+ // Case n >s hi:
+ // (BEFORE) is always false, show (AFTER) is always false.
+ // U(n - lo) <= U(hi - lo)
+ // -- Lemma1 (n > lo) -- -- Lemma1 (hi >= lo, LO-HI) --
+ // n - lo <= hi - lo
+ // n <= hi
+ // n <= hi
+ // Contradicts case assumption, so always false.
+ // QED.
+ //
+ // Note: (CHECK) is stronger in this case than in (a, b, c). We have
+ // had multiple bugs around this case (d) in the past. For example:
+ // - Before JDK-8135069: transform into: n - lo <=u hi - lo
+ // leads to rhs underflow with lo=0 and hi=-1
+ // -> we are coming back to this solution, but instead
+ // of checking lo_type->_hi < hi_type->_lo
+ // we now check: lo_type->_hi < hi_type->_lo - 1
+ // which implies lo <= hi and excludes this bad case.
+ // - Before JDK-8346420: transform into: n - lo hi - lo + x2
+ // n - lo <=u hi - lo
+ x1 = igvn->intcon(0);
+ x2 = igvn->intcon(0);
+ cond = BoolTest::le;
}
- if (igvn->type(adjusted_lim)->is_int()->_lo < 0 &&
- !igvn->C->post_loop_opts_phase()) {
- // If range check elimination applies to this comparison, it includes code to protect from overflows that may
- // cause the main loop to be skipped entirely. Delay this transformation.
- // Example:
- // for (int i = 0; i < limit; i++) {
- // if (i < max_jint && i > min_jint) {...
- // }
- // Comparisons folded as:
- // i - min_jint - 1 outcnt() == 0) {
- igvn->remove_dead_node(lo, PhaseIterGVN::NodeOrigin::Speculative);
- }
- if (adjusted_val->outcnt() == 0) {
- igvn->remove_dead_node(adjusted_val, PhaseIterGVN::NodeOrigin::Speculative);
- }
- if (adjusted_lim->outcnt() == 0) {
- igvn->remove_dead_node(adjusted_lim, PhaseIterGVN::NodeOrigin::Speculative);
- }
- igvn->C->record_for_post_loop_opts_igvn(this);
- return false;
- }
-
- Node* newcmp = igvn->transform(new CmpUNode(adjusted_val, adjusted_lim));
+ // Construct the new check: n - lo + x1 hi - lo + x2
+ Node* lhs = igvn->transform(new SubINode(n, lo));
+ lhs = igvn->transform(new AddINode(lhs, x1));
+ Node* rhs = igvn->transform(new SubINode(hi, lo));
+ rhs = igvn->transform(new AddINode(rhs, x2));
+ Node* newcmp = igvn->transform(new CmpUNode(lhs, rhs));
+ if (succ->Opcode() == Op_IfFalse) { cond = BoolTest::negate_mask(cond); }
Node* newbool = igvn->transform(new BoolNode(newcmp, cond));
- igvn->replace_input_of(dom_iff, 1, igvn->intcon(proj->_con));
- igvn->replace_input_of(this, 1, newbool);
+ // Fold iff1 towards middle, and replace the iff2 condition:
+ igvn->replace_input_of(iff1, 1, igvn->intcon(middle->_con));
+ igvn->replace_input_of(iff2, 1, newbool);
- progress_guard.commit();
- return true;
+ return true; // Success with CmpU
}
// Merge the branches that trap for this If and the dominating If into
diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp
index 7251783d771..adb8ff2dedb 100644
--- a/src/hotspot/share/opto/library_call.cpp
+++ b/src/hotspot/share/opto/library_call.cpp
@@ -666,6 +666,10 @@ bool LibraryCallKit::try_to_inline(int predicate) {
return inline_intpoly_montgomeryMult_P256();
case vmIntrinsics::_intpoly_assign:
return inline_intpoly_assign();
+ case vmIntrinsics::_intpoly_mult_25519:
+ return inline_intpoly_mult_25519();
+ case vmIntrinsics::_intpoly_square_25519:
+ return inline_intpoly_square_25519();
case vmIntrinsics::_encodeISOArray:
case vmIntrinsics::_encodeByteISOArray:
return inline_encodeISOArray(false);
@@ -8373,6 +8377,70 @@ bool LibraryCallKit::inline_intpoly_assign() {
return true;
}
+bool LibraryCallKit::inline_intpoly_mult_25519() {
+ address stubAddr;
+ const char *stubName;
+ assert(UseIntPoly25519Intrinsics, "need intpoly25519 intrinsics support");
+ assert(callee()->signature()->size() == 3, "intpoly_mult_25519 has %d parameters", callee()->signature()->size());
+ stubAddr = StubRoutines::intpoly_mult_25519();
+ stubName = "intpoly_mult_25519";
+
+ if (!stubAddr) return false;
+ null_check_receiver(); // null-check receiver
+ if (stopped()) return true;
+
+ Node* a = argument(1);
+ Node* b = argument(2);
+ Node* r = argument(3);
+
+ a = must_be_not_null(a, true);
+ b = must_be_not_null(b, true);
+ r = must_be_not_null(r, true);
+
+ Node* a_start = array_element_address(a, intcon(0), T_LONG);
+ assert(a_start, "a array is null");
+ Node* b_start = array_element_address(b, intcon(0), T_LONG);
+ assert(b_start, "b array is null");
+ Node* r_start = array_element_address(r, intcon(0), T_LONG);
+ assert(r_start, "r array is null");
+
+ Node* call = make_runtime_call(RC_LEAF | RC_NO_FP,
+ OptoRuntime::intpoly_mult_25519_Type(),
+ stubAddr, stubName, TypePtr::BOTTOM,
+ a_start, b_start, r_start);
+ return true;
+}
+
+bool LibraryCallKit::inline_intpoly_square_25519() {
+ address stubAddr;
+ const char *stubName;
+ assert(UseIntPoly25519Intrinsics, "need intpoly25519 intrinsics support");
+ assert(callee()->signature()->size() == 2, "intpoly_mult_25519 has %d parameters", callee()->signature()->size());
+ stubAddr = StubRoutines::intpoly_square_25519();
+ stubName = "intpoly_square_25519";
+
+ if (!stubAddr) return false;
+ null_check_receiver(); // null-check receiver
+ if (stopped()) return true;
+
+ Node* a = argument(1);
+ Node* r = argument(2);
+
+ a = must_be_not_null(a, true);
+ r = must_be_not_null(r, true);
+
+ Node* a_start = array_element_address(a, intcon(0), T_LONG);
+ assert(a_start, "a array is null");
+ Node* r_start = array_element_address(r, intcon(0), T_LONG);
+ assert(r_start, "r array is null");
+
+ Node* call = make_runtime_call(RC_LEAF | RC_NO_FP,
+ OptoRuntime::intpoly_square_25519_Type(),
+ stubAddr, stubName, TypePtr::BOTTOM,
+ a_start, r_start);
+ return true;
+}
+
//------------------------------inline_digestBase_implCompress-----------------------
//
// Calculate MD5 for single-block byte[] array.
diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp
index 5b46ae832a4..871a6b0d072 100644
--- a/src/hotspot/share/opto/library_call.hpp
+++ b/src/hotspot/share/opto/library_call.hpp
@@ -343,6 +343,8 @@ class LibraryCallKit : public GraphKit {
bool inline_poly1305_processBlocks();
bool inline_intpoly_montgomeryMult_P256();
bool inline_intpoly_assign();
+ bool inline_intpoly_mult_25519();
+ bool inline_intpoly_square_25519();
bool inline_digestBase_implCompress(vmIntrinsics::ID id);
bool inline_keccak(vmIntrinsics::ID id);
bool inline_digestBase_implCompressMB(int predicate);
diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp
index d5737166bb6..4f68ff281a0 100644
--- a/src/hotspot/share/opto/memnode.cpp
+++ b/src/hotspot/share/opto/memnode.cpp
@@ -53,6 +53,7 @@
#include "opto/vectornode.hpp"
#include "utilities/align.hpp"
#include "utilities/copy.hpp"
+#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
#include "utilities/powerOfTwo.hpp"
#include "utilities/vmError.hpp"
@@ -1217,7 +1218,9 @@ Node* LoadNode::can_see_stored_value_through_membars(Node* st, PhaseValues* phas
}
}
- return can_see_stored_value(st, phase);
+ Node* res = can_see_stored_value(st, phase);
+ assert(res == nullptr || is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(type()), "the fold is unsafe");
+ return res;
}
// If st is a store to the same location as this, return the stored value
@@ -1273,7 +1276,22 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const {
return nullptr;
}
}
- return st->in(MemNode::ValueIn);
+
+ // Even if we can see the store, we cannot fold the load if the store is not type safe (e.g.
+ // store a j.l.Object into an array of j.l.String) because folding makes the compiler lose the
+ // type information that the uses of this node may need. This is only necessary for pointers, we
+ // can see the stored value of a LoadS even if it is an int because LoadSNode::Ideal will do the
+ // necessary truncation.
+ // The same phenomenon is not an issue for StoreNodes because they don't use res.
+ Node* res = st->in(MemNode::ValueIn);
+ if (is_Store() || is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(bottom_type())) {
+ return res;
+ }
+
+ // Type-unsafe stores must be due to array polymorphism
+ const TypePtr* adr_type = this->adr_type();
+ assert(adr_type == nullptr || adr_type->isa_aryptr() != nullptr, "unexpected type-unsafe store");
+ return nullptr;
}
// A load from a freshly-created object always returns zero.
diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp
index 5e05d6a6e04..e48acd23b87 100644
--- a/src/hotspot/share/opto/mulnode.cpp
+++ b/src/hotspot/share/opto/mulnode.cpp
@@ -894,7 +894,8 @@ static Node* mask_and_replace_shift_amount(PhaseGVN* phase, Node* shift_node, ui
}
if (replace) {
- shift_node->set_req(2, phase->intcon(masked_shift)); // Replace shift count with masked value.
+ // Replace shift count with masked value and put potential dead nodes on the worklist.
+ shift_node->set_req_X(2, phase->intcon(masked_shift), phase);
// We need to notify the caller that the graph was reshaped, as Ideal needs
// to return the root of the reshaped graph if any change was made.
diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp
index 1afffcadd6e..7f791082b65 100644
--- a/src/hotspot/share/opto/runtime.cpp
+++ b/src/hotspot/share/opto/runtime.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -237,6 +237,8 @@ const TypeFunc* OptoRuntime::_string_IndexOf_Type = nullptr;
const TypeFunc* OptoRuntime::_poly1305_processBlocks_Type = nullptr;
const TypeFunc* OptoRuntime::_intpoly_montgomeryMult_P256_Type = nullptr;
const TypeFunc* OptoRuntime::_intpoly_assign_Type = nullptr;
+const TypeFunc* OptoRuntime::_intpoly_mult_25519_Type = nullptr;
+const TypeFunc* OptoRuntime::_intpoly_square_25519_Type = nullptr;
const TypeFunc* OptoRuntime::_updateBytesCRC32_Type = nullptr;
const TypeFunc* OptoRuntime::_updateBytesCRC32C_Type = nullptr;
const TypeFunc* OptoRuntime::_updateBytesAdler32_Type = nullptr;
@@ -1786,6 +1788,41 @@ static const TypeFunc* make_intpoly_assign_Type() {
return TypeFunc::make(domain, range);
}
+static const TypeFunc* make_intpoly_mult_25519_Type() {
+ int argcnt = 3;
+
+ const Type** fields = TypeTuple::fields(argcnt);
+ int argp = TypeFunc::Parms;
+ fields[argp++] = TypePtr::NOTNULL; // a array
+ fields[argp++] = TypePtr::NOTNULL; // b array
+ fields[argp++] = TypePtr::NOTNULL; // r(esult) array
+ assert(argp == TypeFunc::Parms + argcnt, "correct decoding");
+ const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms+argcnt, fields);
+
+ // result type needed
+ fields = TypeTuple::fields(1);
+ fields[TypeFunc::Parms + 0] = nullptr; // void
+ const TypeTuple* range = TypeTuple::make(TypeFunc::Parms, fields);
+ return TypeFunc::make(domain, range);
+}
+
+static const TypeFunc* make_intpoly_square_25519_Type() {
+ int argcnt = 2;
+
+ const Type** fields = TypeTuple::fields(argcnt);
+ int argp = TypeFunc::Parms;
+ fields[argp++] = TypePtr::NOTNULL; // a array
+ fields[argp++] = TypePtr::NOTNULL; // r(esult) array
+ assert(argp == TypeFunc::Parms + argcnt, "correct decoding");
+ const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms+argcnt, fields);
+
+ // result type needed
+ fields = TypeTuple::fields(1);
+ fields[TypeFunc::Parms + 0] = nullptr; // void
+ const TypeTuple* range = TypeTuple::make(TypeFunc::Parms, fields);
+ return TypeFunc::make(domain, range);
+}
+
//------------- Interpreter state for on stack replacement
static const TypeFunc* make_osr_end_Type() {
// create input type (domain)
@@ -2354,6 +2391,8 @@ void OptoRuntime::initialize_types() {
_poly1305_processBlocks_Type = make_poly1305_processBlocks_Type();
_intpoly_montgomeryMult_P256_Type = make_intpoly_montgomeryMult_P256_Type();
_intpoly_assign_Type = make_intpoly_assign_Type();
+ _intpoly_mult_25519_Type = make_intpoly_mult_25519_Type();
+ _intpoly_square_25519_Type = make_intpoly_square_25519_Type();
_updateBytesCRC32_Type = make_updateBytesCRC32_Type();
_updateBytesCRC32C_Type = make_updateBytesCRC32C_Type();
_updateBytesAdler32_Type = make_updateBytesAdler32_Type();
diff --git a/src/hotspot/share/opto/runtime.hpp b/src/hotspot/share/opto/runtime.hpp
index af8a206e10c..5802bf59ae5 100644
--- a/src/hotspot/share/opto/runtime.hpp
+++ b/src/hotspot/share/opto/runtime.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -190,6 +190,8 @@ class OptoRuntime : public AllStatic {
static const TypeFunc* _poly1305_processBlocks_Type;
static const TypeFunc* _intpoly_montgomeryMult_P256_Type;
static const TypeFunc* _intpoly_assign_Type;
+ static const TypeFunc* _intpoly_mult_25519_Type;
+ static const TypeFunc* _intpoly_square_25519_Type;
static const TypeFunc* _updateBytesCRC32_Type;
static const TypeFunc* _updateBytesCRC32C_Type;
static const TypeFunc* _updateBytesAdler32_Type;
@@ -687,6 +689,18 @@ private:
return _intpoly_assign_Type;
}
+ // IntegerPolynomial25519 multiply function
+ static inline const TypeFunc* intpoly_mult_25519_Type() {
+ assert(_intpoly_mult_25519_Type != nullptr, "should be initialized");
+ return _intpoly_mult_25519_Type;
+ }
+
+ // IntegerPolynomial25519 square function
+ static inline const TypeFunc* intpoly_square_25519_Type() {
+ assert(_intpoly_square_25519_Type != nullptr, "should be initialized");
+ return _intpoly_square_25519_Type;
+ }
+
/**
* int updateBytesCRC32(int crc, byte* b, int len)
*/
diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp
index 014f41f82cc..39d5f0be90c 100644
--- a/src/hotspot/share/opto/subnode.cpp
+++ b/src/hotspot/share/opto/subnode.cpp
@@ -1921,6 +1921,31 @@ bool BoolNode::is_counted_loop_exit_test() {
return false;
}
+template
+static const IntegerType* integral_abs_value(const IntegerType* t) {
+ typedef typename IntegerType::NativeUType NativeUType;
+
+ // Find the absolute value of a type, resulting in a range that fits inside the unsigned range [0, signed_max+1].
+ // The possible values of a TypeInteger is described with the following range in the signed domain:
+ // smin----------lo=======uhi--------0--------ulo===========hi----------smax
+
+ // To find the absolute value of the range, we find the closer (min) value of uhi and ulo to 0, and the further (max)
+ // value of lo and hi from 0. In the unsigned domain, the resulting range looks like this:
+ // 0-----------min(|ulo|,|uhi|)================max(|lo|,|hi|)-----------umax
+
+ // When the input range's hi and lo are both positive or negative, lo == ulo and hi == uhi:
+ // smin------------------------------0-------lo===========hi------------smax (Positive)
+ // smin--------lo===========hi-------0----------------------------------smax (Negative)
+
+ // For these ranges, the result in the unsigned domain is simply [min(|lo|, |hi|), max(|lo|, |hi|)]:
+ // 0-----------min(|lo|,|hi|)==================max(|lo|,|hi|)-----------umax
+
+ NativeUType umin = MIN2(g_uabs(t->_ulo), g_uabs(t->_uhi));
+ NativeUType umax = MAX2(g_uabs(t->_lo), g_uabs(t->_hi));
+
+ return IntegerType::make_unsigned(umin, umax, t->_widen);
+}
+
//=============================================================================
//------------------------------Value------------------------------------------
const Type* AbsNode::Value(PhaseGVN* phase) const {
@@ -1930,17 +1955,13 @@ const Type* AbsNode::Value(PhaseGVN* phase) const {
switch (t1->base()) {
case Type::Int: {
const TypeInt* ti = t1->is_int();
- if (ti->is_con()) {
- return TypeInt::make(g_uabs(ti->get_con()));
- }
- break;
+
+ return integral_abs_value(ti);
}
case Type::Long: {
const TypeLong* tl = t1->is_long();
- if (tl->is_con()) {
- return TypeLong::make(g_uabs(tl->get_con()));
- }
- break;
+
+ return integral_abs_value(tl);
}
case Type::FloatCon:
return TypeF::make(abs(t1->getf()));
diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp
index 29ec25b41f8..358508248d0 100644
--- a/src/hotspot/share/opto/subnode.hpp
+++ b/src/hotspot/share/opto/subnode.hpp
@@ -334,8 +334,11 @@ struct BoolTest {
static mask negate_mask(mask btm) { return mask(btm ^ 4); }
static mask unsigned_mask(mask btm);
bool is_canonical( ) const { return (_test == BoolTest::ne || _test == BoolTest::lt || _test == BoolTest::le || _test == BoolTest::overflow); }
- bool is_less( ) const { return _test == BoolTest::lt || _test == BoolTest::le; }
- bool is_greater( ) const { return _test == BoolTest::gt || _test == BoolTest::ge; }
+ bool is_less( ) const { return is_less(_test); }
+ bool is_greater( ) const { return is_greater(_test); }
+ static bool is_less(mask btm) { return btm == BoolTest::lt || btm == BoolTest::le; }
+ static bool is_greater(mask btm) { return btm == BoolTest::gt || btm == BoolTest::ge; }
+
void dump_on(outputStream *st) const;
mask merge(BoolTest other) const;
};
diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp
index 6c48ad470c9..a1e213319b7 100644
--- a/src/hotspot/share/opto/type.cpp
+++ b/src/hotspot/share/opto/type.cpp
@@ -22,6 +22,7 @@
*
*/
+#include "ci/ciInstanceKlass.hpp"
#include "ci/ciMethodData.hpp"
#include "ci/ciTypeFlow.hpp"
#include "classfile/javaClasses.hpp"
@@ -1800,6 +1801,12 @@ const TypeInt* TypeInt::make(jint lo, jint hi, int widen) {
return make_or_top(TypeIntPrototype{{lo, hi}, {0, max_juint}, {0, 0}}, widen)->is_int();
}
+const TypeInt* TypeInt::make_unsigned(juint ulo, juint uhi, int widen) {
+ assert(ulo <= uhi, "must be legal bounds");
+ // By creating the TypeInt with the full signed range and the given unsigned range, the signed bounds are inferred from the unsigned bounds.
+ return make_or_top(TypeIntPrototype{{min_jint, max_jint}, {ulo, uhi}, {0, 0}}, widen)->is_int();
+}
+
const Type* TypeInt::make_or_top(const TypeIntPrototype& t, int widen) {
return make_or_top(t, widen, false);
}
@@ -1935,6 +1942,12 @@ const TypeLong* TypeLong::make(jlong lo, jlong hi, int widen) {
return make_or_top(TypeIntPrototype{{lo, hi}, {0, max_julong}, {0, 0}}, widen)->is_long();
}
+const TypeLong* TypeLong::make_unsigned(julong ulo, julong uhi, int widen) {
+ assert(ulo <= uhi, "must be legal bounds");
+ // By creating the TypeLong with the full signed range and the given unsigned range, the signed bounds are inferred from the unsigned bounds.
+ return make_or_top(TypeIntPrototype{{min_jlong, max_jlong}, {ulo, uhi}, {0, 0}}, widen)->is_long();
+}
+
const Type* TypeLong::make_or_top(const TypeIntPrototype& t, int widen) {
return make_or_top(t, widen, false);
}
@@ -3280,6 +3293,17 @@ bool TypeInterfaces::eq(ciInstanceKlass* k) const {
return true;
}
+// Check whether an instance of type k will satisfy this
+bool TypeInterfaces::is_subset(ciInstanceKlass* k) const {
+ assert(k->is_loaded(), "should be loaded");
+ GrowableArray* k_interfaces = k->transitive_interfaces();
+ for (int i = 0; i < _interfaces.length(); i++) {
+ if (!k_interfaces->contains(_interfaces.at(i))) {
+ return false;
+ }
+ }
+ return true;
+}
uint TypeInterfaces::hash() const {
assert(_initialized, "must be");
@@ -5958,20 +5982,16 @@ const TypeKlassPtr* TypeInstKlassPtr::try_improve() const {
Compile* C = Compile::current();
Dependencies* deps = C->dependencies();
assert((deps != nullptr) == (C->method() != nullptr && C->method()->code_size() > 0), "sanity");
- const TypeInterfaces* interfaces = _interfaces;
if (k->is_loaded()) {
ciInstanceKlass* ik = k->as_instance_klass();
bool klass_is_exact = ik->is_final();
- if (!klass_is_exact &&
- deps != nullptr) {
+ if (!klass_is_exact && deps != nullptr) {
ciInstanceKlass* sub = ik->unique_concrete_subklass();
- if (sub != nullptr) {
- if (_interfaces->eq(sub)) {
- deps->assert_abstract_with_unique_concrete_subtype(ik, sub);
- k = ik = sub;
- klass_is_exact = sub->is_final();
- return TypeKlassPtr::make(klass_is_exact ? Constant : _ptr, k, _offset);
- }
+ if (sub != nullptr && _interfaces->is_subset(sub)) {
+ deps->assert_abstract_with_unique_concrete_subtype(ik, sub);
+ k = ik = sub;
+ klass_is_exact = sub->is_final();
+ return TypeKlassPtr::make(klass_is_exact ? Constant : _ptr, k, _offset);
}
}
}
diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp
index 3e029c387b1..06201113311 100644
--- a/src/hotspot/share/opto/type.hpp
+++ b/src/hotspot/share/opto/type.hpp
@@ -782,6 +782,7 @@ protected:
public:
typedef jint NativeType;
+ typedef juint NativeUType;
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
@@ -795,6 +796,7 @@ public:
static const TypeInt* make(jint con);
// must always specify w
static const TypeInt* make(jint lo, jint hi, int widen);
+ static const TypeInt* make_unsigned(juint ulo, juint uhi, int widen);
static const Type* make_or_top(const TypeIntPrototype& t, int widen);
static const TypeInt* make(const TypeIntPrototype& t, int widen) { return make_or_top(t, widen)->is_int(); }
static const TypeInt* make(const TypeIntMirror& t, int widen) {
@@ -871,6 +873,7 @@ protected:
virtual const Type* filter_helper(const Type* kills, bool include_speculative) const;
public:
typedef jlong NativeType;
+ typedef julong NativeUType;
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
@@ -885,6 +888,7 @@ public:
static const TypeLong* make(jlong con);
// must always specify w
static const TypeLong* make(jlong lo, jlong hi, int widen);
+ static const TypeLong* make_unsigned(julong ulo, julong uhi, int widen);
static const Type* make_or_top(const TypeIntPrototype& t, int widen);
static const TypeLong* make(const TypeIntPrototype& t, int widen) { return make_or_top(t, widen)->is_long(); }
static const TypeLong* make(const TypeIntMirror& t, int widen) {
@@ -1137,6 +1141,7 @@ public:
static const TypeInterfaces* make(GrowableArray* interfaces = nullptr);
bool eq(const Type* other) const;
bool eq(ciInstanceKlass* k) const;
+ bool is_subset(ciInstanceKlass* k) const;
uint hash() const;
const Type *xdual() const;
void dump(outputStream* st) const;
diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp
index 8e0ca927a16..96ac4d78dcd 100644
--- a/src/hotspot/share/opto/vectorization.cpp
+++ b/src/hotspot/share/opto/vectorization.cpp
@@ -1744,8 +1744,8 @@ AlignmentSolution* AlignmentSolver::solve() const {
// And since abs(C_pre) < aw, the solutions of (4a, b, c) can now only be constrained or empty.
// But since we already handled the empty case, the solutions are now all constrained.
assert(eq4a_state == EQ4::State::CONSTRAINED &&
- eq4a_state == EQ4::State::CONSTRAINED &&
- eq4a_state == EQ4::State::CONSTRAINED, "all must be constrained now");
+ eq4b_state == EQ4::State::CONSTRAINED &&
+ eq4c_state == EQ4::State::CONSTRAINED, "all must be constrained now");
// And since they are all constrained, we must have:
//
diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp
index 1059bfe20e5..dd49d88ce96 100644
--- a/src/hotspot/share/opto/vectornode.cpp
+++ b/src/hotspot/share/opto/vectornode.cpp
@@ -1168,6 +1168,32 @@ static bool is_commutative_vector_operation(int opcode) {
}
}
+static bool is_associative_and_commutative_vector_operation(int opcode) {
+ switch (opcode) {
+ case Op_AddVB:
+ case Op_AddVS:
+ case Op_AddVI:
+ case Op_AddVL:
+ case Op_MulVB:
+ case Op_MulVS:
+ case Op_MulVI:
+ case Op_MulVL:
+ case Op_MaxV:
+ case Op_MinV:
+ case Op_UMinV:
+ case Op_UMaxV:
+ case Op_XorV:
+ case Op_OrV:
+ case Op_AndV:
+ case Op_AndVMask:
+ case Op_OrVMask:
+ case Op_XorVMask:
+ return true;
+ default:
+ return false;
+ }
+}
+
bool VectorNode::should_swap_inputs_to_help_global_value_numbering() {
// Predicated vector operations are sensitive to ordering of inputs.
// When the mask corresponding to a vector lane is false then
@@ -1299,7 +1325,7 @@ Node* VectorNode::create_reassociated_node(Node* parent, Node* child, Node* cinp
return cloned_parent;
}
-// Try to reassociate commutative vector operations using the following ideal transformation,
+// Try to reassociate associative vector operations using the following ideal transformation,
// this will facilitate strength reducing a vector operation with all replicated inputs to
// a scalar operation.
//
@@ -1312,8 +1338,8 @@ Node* VectorNode::reassociate_vector_operation(PhaseGVN* phase) {
return nullptr;
}
- // Enable re-association for commutative vector operations.
- if (!is_commutative_vector_operation(Opcode())) {
+ // Enable re-association only for associative and commutative vector operations.
+ if (!is_associative_and_commutative_vector_operation(Opcode())) {
return nullptr;
}
@@ -2701,9 +2727,7 @@ Node* XorVNode::Ideal_XorV_VectorMaskCmp(PhaseGVN* phase, bool can_reshape) {
Node* in1 = in(1);
Node* in2 = in(2);
// Transformations for predicated vectors are not supported for now.
- if (is_predicated_vector() ||
- in1->is_predicated_vector() ||
- in2->is_predicated_vector()) {
+ if (is_predicated_vector()) {
return nullptr;
}
@@ -2727,6 +2751,7 @@ Node* XorVNode::Ideal_XorV_VectorMaskCmp(PhaseGVN* phase, bool can_reshape) {
}
if (in1->Opcode() != Op_VectorMaskCmp ||
in1->outcnt() != 1 ||
+ in1->is_predicated_vector() ||
!in1->as_VectorMaskCmp()->predicate_can_be_negated() ||
!VectorNode::is_all_ones_vector(in2)) {
return nullptr;
diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp
index 963c5497b13..f9ebf3b066c 100644
--- a/src/hotspot/share/prims/jvmtiExport.cpp
+++ b/src/hotspot/share/prims/jvmtiExport.cpp
@@ -2269,6 +2269,8 @@ void JvmtiExport::post_field_access_by_jni(JavaThread *thread, oop obj,
RegisterMap::ProcessFrames::skip,
RegisterMap::WalkContinuation::skip);
javaVFrame *jvf = thread->last_java_vframe(®_map);
+ assert(jvf != nullptr, "last frame shouldn't be null");
+
Method* method = jvf->method();
address address = jvf->method()->code_base();
@@ -2367,6 +2369,8 @@ void JvmtiExport::post_field_modification_by_jni(JavaThread *thread, oop obj,
RegisterMap::ProcessFrames::skip,
RegisterMap::WalkContinuation::skip);
javaVFrame *jvf = thread->last_java_vframe(®_map);
+ assert(jvf != nullptr, "last frame shouldn't be null");
+
Method* method = jvf->method();
address address = jvf->method()->code_base();
diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp
index b7e12fc1c92..2c389d37fec 100644
--- a/src/hotspot/share/prims/whitebox.cpp
+++ b/src/hotspot/share/prims/whitebox.cpp
@@ -747,6 +747,14 @@ WB_ENTRY(void, WB_NMTArenaMalloc(JNIEnv* env, jobject o, jlong arena, jlong size
a->Amalloc(size_t(size));
WB_END
+WB_ENTRY(jboolean, WB_isC2Included(JNIEnv* env))
+#ifdef COMPILER2
+ return true;
+#else
+ return false;
+#endif
+WB_END
+
static jmethodID reflected_method_to_jmid(JavaThread* thread, JNIEnv* env, jobject method) {
assert(method != nullptr, "method should not be null");
ThreadToNativeFromVM ttn(thread);
@@ -2843,6 +2851,7 @@ static JNINativeMethod methods[] = {
{CC"NMTNewArena", CC"(J)J", (void*)&WB_NMTNewArena },
{CC"NMTFreeArena", CC"(J)V", (void*)&WB_NMTFreeArena },
{CC"NMTArenaMalloc", CC"(JJ)V", (void*)&WB_NMTArenaMalloc },
+ {CC"isC2Included", CC"()Z", (void*)&WB_isC2Included },
{CC"deoptimizeFrames", CC"(Z)I", (void*)&WB_DeoptimizeFrames },
{CC"isFrameDeoptimized", CC"(I)Z", (void*)&WB_IsFrameDeoptimized},
{CC"deoptimizeAll", CC"()V", (void*)&WB_DeoptimizeAll },
diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp
index a607484d02a..2804224ed01 100644
--- a/src/hotspot/share/runtime/arguments.cpp
+++ b/src/hotspot/share/runtime/arguments.cpp
@@ -544,25 +544,6 @@ static SpecialFlag const special_jvm_flags[] = {
{ "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(27), JDK_Version::undefined() },
#endif
- { "PSChunkLargeArrays", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "ParallelRefProcEnabled", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "ParallelRefProcBalancingEnabled", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "MaxRAM", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "NewSizeThreadIncrease", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "NeverActAsServerClassMachine", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "AlwaysActAsServerClassMachine", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "UseXMMForArrayCopy", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "UseNewLongLShift", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- { "AggressiveHeap", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
-
- {"ShenandoahAccelerationSamplePeriod", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- {"ShenandoahRateAccelerationSampleSize", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- {"ShenandoahMomentaryAllocationRateSpikeSampleSize", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- {"ShenandoahAdaptiveSampleFrequencyHz", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- {"ShenandoahAdaptiveSampleSizeSeconds", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- {"ShenandoahAdaptiveInitialSpikeThreshold",JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
- {"ShenandoahAdaptiveDecayFactor", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
-
#ifdef ASSERT
{ "DummyObsoleteTestFlag", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::undefined() },
#endif
@@ -2195,11 +2176,9 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin
if (FLAG_SET_CMDLINE(ThreadStackSize, value) != JVMFlag::SUCCESS) {
return JNI_EINVAL;
}
- } else if (match_option(option, "-Xmaxjitcodesize", &tail) ||
- match_option(option, "-XX:ReservedCodeCacheSize=", &tail)) {
- if (match_option(option, "-Xmaxjitcodesize", &tail)) {
- warning("Option -Xmaxjitcodesize was deprecated in JDK 26 and will likely be removed in a future release.");
- }
+ } else if (match_option(option, "-Xmaxjitcodesize", &tail)) {
+ warning("Ignoring option %s; support was removed in JDK 27", option->optionString);
+ } else if (match_option(option, "-XX:ReservedCodeCacheSize=", &tail)) {
julong long_ReservedCodeCacheSize = 0;
ArgsRange errcode = parse_memory_size(tail, &long_ReservedCodeCacheSize, 1);
diff --git a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp
index 0b7e64a3ba6..f8af2545c37 100644
--- a/src/hotspot/share/runtime/continuation.cpp
+++ b/src/hotspot/share/runtime/continuation.cpp
@@ -87,7 +87,8 @@ class UnmountBeginMark : public StackObj {
}
~UnmountBeginMark() {
assert(!_current->is_suspended()
- JVMTI_ONLY(|| (_current->is_vthread_transition_disabler() && _result != freeze_ok)), "must be");
+ JVMTI_ONLY(|| (_result != freeze_ok &&
+ (_current->is_vthread_transition_disabler() || _current->is_disable_suspend()))), "must be");
assert(_current->is_in_vthread_transition(), "must be");
if (_result != freeze_ok) {
@@ -152,12 +153,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;
@@ -387,9 +382,9 @@ 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
+ log_develop_debug(continuations)("continuation_bottom_sender: [" UINT64_FORMAT "] [%d] callee: " INTPTR_FORMAT
" sender_sp: " INTPTR_FORMAT,
- java_tid(thread), thread->osthread()->thread_id(), p2i(callee.sp()), p2i(sender_sp));
+ thread->monitor_owner_id(), 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/frame.cpp b/src/hotspot/share/runtime/frame.cpp
index a1c33f4fda0..b983b4648d4 100644
--- a/src/hotspot/share/runtime/frame.cpp
+++ b/src/hotspot/share/runtime/frame.cpp
@@ -1237,9 +1237,6 @@ void frame::interpreter_frame_verify_monitor(BasicObjectLock* value) const {
#ifndef PRODUCT
-// Returns true iff the address p is readable and *(intptr_t*)p != errvalue
-extern "C" bool dbg_is_safe(const void* p, intptr_t errvalue);
-
class FrameValuesOopClosure: public OopClosure, public DerivedOopClosure {
private:
GrowableArray* _oops;
@@ -1269,17 +1266,13 @@ public:
_derived->push(derived_loc);
}
- bool is_good(oop* p) {
- return *p == nullptr || (dbg_is_safe(*p, -1) && dbg_is_safe((*p)->klass_without_asserts(), -1) && oopDesc::is_oop_or_null(*p));
- }
void describe(FrameValues& values, int frame_no) {
for (int i = 0; i < _oops->length(); i++) {
oop* p = _oops->at(i);
- values.describe(frame_no, (intptr_t*)p, err_msg("oop%s for #%d", is_good(p) ? "" : " (BAD)", frame_no));
+ values.describe(frame_no, (intptr_t*)p, err_msg("oop for #%d", frame_no));
}
for (int i = 0; i < _narrow_oops->length(); i++) {
narrowOop* p = _narrow_oops->at(i);
- // we can't check for bad compressed oops, as decoding them might crash
values.describe(frame_no, (intptr_t*)p, err_msg("narrow oop for #%d", frame_no));
}
assert(_base->length() == _derived->length(), "should be the same");
diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp
index f90a644eaa4..ec34305f837 100644
--- a/src/hotspot/share/runtime/globals.hpp
+++ b/src/hotspot/share/runtime/globals.hpp
@@ -229,9 +229,13 @@ const int ObjectAlignmentInBytes = 8;
\
product(bool, UsePoly1305Intrinsics, false, DIAGNOSTIC, \
"Use intrinsics for sun.security.util.math.intpoly") \
- product(bool, UseIntPolyIntrinsics, false, DIAGNOSTIC, \
+ \
+ product(bool, UseIntPolyIntrinsics, false, DIAGNOSTIC, \
"Use intrinsics for sun.security.util.math.intpoly.MontgomeryIntegerPolynomialP256") \
\
+ product(bool, UseIntPoly25519Intrinsics, false, DIAGNOSTIC, \
+ "Use intrinsics for sun.security.util.math.intpoly.IntegerPolynomial25519") \
+ \
product(size_t, LargePageSizeInBytes, 0, \
"Maximum large page size used (0 will use the default large " \
"page size for the environment as the maximum) " \
diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp
index 08d3cde8562..9e3c629ad78 100644
--- a/src/hotspot/share/runtime/javaThread.hpp
+++ b/src/hotspot/share/runtime/javaThread.hpp
@@ -873,7 +873,7 @@ public:
// Atomic version; invoked by a thread other than the owning thread.
bool in_critical_atomic() { return AtomicAccess::load(&_jni_active_critical) > 0; }
- bool jni_deferred_suspension() { return AtomicAccess::load(&_jni_deferred_suspension_count); }
+ bool jni_deferred_suspension() const { return AtomicAccess::load(&_jni_deferred_suspension_count); }
inline void enter_jni_deferred_suspension();
void exit_jni_deferred_suspension() {
precond(Thread::current() == this);
diff --git a/src/hotspot/share/runtime/mountUnmountDisabler.cpp b/src/hotspot/share/runtime/mountUnmountDisabler.cpp
index 65a82d6c563..277a841a88c 100644
--- a/src/hotspot/share/runtime/mountUnmountDisabler.cpp
+++ b/src/hotspot/share/runtime/mountUnmountDisabler.cpp
@@ -129,7 +129,7 @@ bool MountUnmountDisabler::is_start_transition_disabled(JavaThread* thread, oop
int base_disable_count = notify_jvmti_events() ? 1 : 0;
return java_lang_Thread::vthread_transition_disable_count(vthread) > 0
|| global_vthread_transition_disable_count() > base_disable_count
- JVMTI_ONLY(|| (!thread->is_vthread_transition_disabler() &&
+ JVMTI_ONLY(|| (!thread->is_vthread_transition_disabler() && !thread->is_disable_suspend() &&
(JvmtiVTSuspender::is_vthread_suspended(java_lang_Thread::thread_id(vthread)) || thread->is_suspended())));
}
diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp
index f6c7f03778b..10a8dd6f858 100644
--- a/src/hotspot/share/runtime/os.hpp
+++ b/src/hotspot/share/runtime/os.hpp
@@ -440,9 +440,9 @@ class os: AllStatic {
public:
// get allowed minimum java stack size
static jlong get_minimum_java_stack_size();
- // Find committed memory region within specified range (start, start + size),
- // return true if found any
- static bool committed_in_range(address start, size_t size, address& committed_start, size_t& committed_size);
+ // Find the first resident memory region within the specified range (start, start + size) beginning at the start address.
+ // Returns true if successful or false if none are found.
+ static bool first_resident_in_range(address start, size_t size, address& resident_start, size_t& resident_size);
// OS interface to Virtual Memory
diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp
index bef6a0c27f0..5c3567eb0c0 100644
--- a/src/hotspot/share/runtime/stubDeclarations.hpp
+++ b/src/hotspot/share/runtime/stubDeclarations.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, Red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -801,6 +801,12 @@
intpoly_montgomeryMult_P256, intpoly_montgomeryMult_P256) \
do_stub(compiler, intpoly_assign) \
do_entry(compiler, intpoly_assign, intpoly_assign, intpoly_assign) \
+ do_stub(compiler, intpoly_mult_25519) \
+ do_entry(compiler, intpoly_mult_25519, \
+ intpoly_mult_25519, intpoly_mult_25519) \
+ do_stub(compiler, intpoly_square_25519) \
+ do_entry(compiler, intpoly_square_25519, \
+ intpoly_square_25519, intpoly_square_25519) \
do_stub(compiler, md5_implCompress) \
do_entry(compiler, md5_implCompress, md5_implCompress, \
md5_implCompress) \
diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp
index 97ceb19d0ad..b720871c389 100644
--- a/src/hotspot/share/services/diagnosticCommand.hpp
+++ b/src/hotspot/share/services/diagnosticCommand.hpp
@@ -654,17 +654,20 @@ public:
// VM.systemdictionary -verbose: for dumping the system dictionary table
//
class VM_DumpHashtable : public VM_Operation {
+public:
+ enum DumpKind {
+ DumpSymbols,
+ DumpStrings,
+ DumpSysDict
+ };
+
private:
outputStream* _out;
- int _which;
+ DumpKind _which;
bool _verbose;
+
public:
- enum {
- DumpSymbols = 1 << 0,
- DumpStrings = 1 << 1,
- DumpSysDict = 1 << 2 // not implemented yet
- };
- VM_DumpHashtable(outputStream* out, int which, bool verbose) {
+ VM_DumpHashtable(outputStream* out, DumpKind which, bool verbose) {
_out = out;
_which = which;
_verbose = verbose;
diff --git a/src/hotspot/share/services/diagnosticFramework.hpp b/src/hotspot/share/services/diagnosticFramework.hpp
index 5f9253b492e..5fd0182fd77 100644
--- a/src/hotspot/share/services/diagnosticFramework.hpp
+++ b/src/hotspot/share/services/diagnosticFramework.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -85,6 +85,9 @@ public:
// instance to the caller.
return line;
}
+ size_t cursor() const {
+ return _cursor;
+ }
};
// Iterator class to iterate over diagnostic command arguments
diff --git a/src/hotspot/share/utilities/concurrentHashTable.hpp b/src/hotspot/share/utilities/concurrentHashTable.hpp
index d2317847307..dfba84ca519 100644
--- a/src/hotspot/share/utilities/concurrentHashTable.hpp
+++ b/src/hotspot/share/utilities/concurrentHashTable.hpp
@@ -534,11 +534,6 @@ class ConcurrentHashTable : public CHeapObj {
template
void bulk_delete(Thread* thread, EVALUATE_FUNC& eval_f, DELETE_FUNC& del_f);
- // Gets statistics if available, if not return old one. Item sizes are calculated with
- // VALUE_SIZE_FUNC.
- template
- TableStatistics statistics_get(Thread* thread, VALUE_SIZE_FUNC& vs_f, TableStatistics old);
-
// Moves all nodes from this table to to_cht with new hash code.
// Must be done at a safepoint.
void rehash_nodes_to(Thread* thread, ConcurrentHashTable* to_cht);
diff --git a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp
index d5f6dee336b..312d118a647 100644
--- a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp
+++ b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp
@@ -1266,22 +1266,6 @@ inline TableStatistics ConcurrentHashTable::
}
}
-template
-template
-inline TableStatistics ConcurrentHashTable::
- statistics_get(Thread* thread, VALUE_SIZE_FUNC& vs_f, TableStatistics old)
-{
- if (!try_resize_lock(thread)) {
- return old;
- }
- InternalTable* table = get_table();
- NumberSeq summary;
- size_t literal_bytes = 0;
-
- internal_statistics_range(thread, 0, table->_size, vs_f, summary, literal_bytes);
- return internal_statistics_epilog(thread, summary, literal_bytes);
-}
-
template
inline void ConcurrentHashTable::
rehash_nodes_to(Thread* thread, ConcurrentHashTable* to_cht)
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java
index 6bd70c3cdd6..96a1eb686cc 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java
@@ -46,14 +46,17 @@ public final class ML_KEM {
private static final int XOF_PAD = 24;
private static final int MONT_R_BITS = 20;
private static final int MONT_Q = 3329;
- private static final int MONT_R_SQUARE_MOD_Q = 152;
private static final int MONT_Q_INV_MOD_R = 586497;
// toMont((ML_KEM_N / 2)^-1 mod ML_KEM_Q) using R = 2^MONT_R_BITS
private static final int MONT_DIM_HALF_INVERSE = 1534;
private static final int BARRETT_MULTIPLIER = 20159;
+ private static final int BARRETT_ADDEND = 1665;
private static final int BARRETT_SHIFT = 26;
- private static final int[] MONT_ZETAS_FOR_NTT = new int[]{
+
+ // The values from Appendix A of the FIPS 203 standard converted to the
+ // Montgomery domain, i.e. toMont(zeta^ (bitrev_7(i)) for i = 0..127
+ private static final int[] MONT_ZETAS_FOR_NTT = new int[] {
1188, 914, -969, 585, -551, 1263, -97, 593,
-35, -1400, -417, -1253, 742, -281, 185, -819,
-1226, 895, -530, 52, 25, 1000, 1249, -909,
@@ -72,7 +75,7 @@ public final class ML_KEM {
-1599, -709, -789, -1317, -57, 1049, -584
};
- private static final short[] montZetasForVectorNttArr = new short[]{
+ private static final short[] montZetasForVectorNttArr = new short[] {
// level 0
-758, -758, -758, -758, -758, -758, -758, -758,
-758, -758, -758, -758, -758, -758, -758, -758,
@@ -193,26 +196,8 @@ public final class ML_KEM {
-108, -108, -308, -308, 996, 996, 991, 991,
958, 958, -1460, -1460, 1522, 1522, 1628, 1628
};
- private static final int[] MONT_ZETAS_FOR_INVERSE_NTT = new int[]{
- 584, -1049, 57, 1317, 789, 709, 1599, -1601,
- -990, 604, 348, 857, 612, 474, 1177, -1014,
- -88, -982, -191, 668, 1386, 486, -1153, -534,
- 514, 137, 586, -1178, 227, 339, -907, 244,
- 1200, -833, 1394, -30, 1074, 636, -317, -1192,
- -1259, -355, -425, -884, -977, 1430, 868, 607,
- 184, 1448, 702, 1327, 431, 497, 595, -94,
- 1649, -1497, -620, 42, -172, 1107, -222, 1003,
- 426, -845, 395, -510, 1613, 825, 1269, -290,
- -1429, 623, -567, 1617, 36, 1007, 1440, 332,
- -201, 1313, -1382, -744, 669, -1538, 128, -1598,
- 1401, 1183, -553, 714, 405, -1155, -445, 406,
- -1496, -49, 82, 1369, 259, 1604, 373, 909,
- -1249, -1000, -25, -52, 530, -895, 1226, 819,
- -185, 281, -742, 1253, 417, 1400, 35, -593,
- 97, -1263, 551, -585, 969, -914, -1188
- };
- private static final short[] montZetasForVectorInverseNttArr = new short[]{
+ private static final short[] montZetasForVectorInverseNttArr = new short[] {
// level 0
-1628, -1628, -1522, -1522, 1460, 1460, -958, -958,
-991, -991, -996, -996, 308, 308, 108, 108,
@@ -334,25 +319,28 @@ public final class ML_KEM {
758, 758, 758, 758, 758, 758, 758, 758
};
- private static final int[] MONT_ZETAS_FOR_NTT_MULT = new int[]{
- -1003, 1003, 222, -222, -1107, 1107, 172, -172,
- -42, 42, 620, -620, 1497, -1497, -1649, 1649,
- 94, -94, -595, 595, -497, 497, -431, 431,
- -1327, 1327, -702, 702, -1448, 1448, -184, 184,
- -607, 607, -868, 868, -1430, 1430, 977, -977,
- 884, -884, 425, -425, 355, -355, 1259, -1259,
- 1192, -1192, 317, -317, -636, 636, -1074, 1074,
- 30, -30, -1394, 1394, 833, -833, -1200, 1200,
- -244, 244, 907, -907, -339, 339, -227, 227,
- 1178, -1178, -586, 586, -137, 137, -514, 514,
- 534, -534, 1153, -1153, -486, 486, -1386, 1386,
- -668, 668, 191, -191, 982, -982, 88, -88,
- 1014, -1014, -1177, 1177, -474, 474, -612, 612,
- -857, 857, -348, 348, -604, 604, 990, -990,
- 1601, -1601, -1599, 1599, -709, 709, -789, 789,
- -1317, 1317, -57, 57, 1049, -1049, -584, 584
+ // modulo MLKEM_Q positive equivalents of the values listed for
+ // the MultiplyNTTs algorithm in the FIPS 203 standard
+ private static final int[] ZETAS_FOR_NTT_MULT = new int[] {
+ 17, 3312, 2761, 568, 583, 2746, 2649, 680,
+ 1637, 1692, 723, 2606, 2288, 1041, 1100, 2229,
+ 1409, 1920, 2662, 667, 3281, 48, 233, 3096,
+ 756, 2573, 2156, 1173, 3015, 314, 3050, 279,
+ 1703, 1626, 1651, 1678, 2789, 540, 1789, 1540,
+ 1847, 1482, 952, 2377, 1461, 1868, 2687, 642,
+ 939, 2390, 2308, 1021, 2437, 892, 2388, 941,
+ 733, 2596, 2337, 992, 268, 3061, 641, 2688,
+ 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109,
+ 375, 2954, 2549, 780, 2090, 1239, 1645, 1684,
+ 1063, 2266, 319, 3010, 2773, 556, 757, 2572,
+ 2099, 1230, 561, 2768, 2466, 863, 2594, 735,
+ 2804, 525, 1092, 2237, 403, 2926, 1026, 2303,
+ 1143, 2186, 2150, 1179, 2775, 554, 886, 2443,
+ 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300,
+ 2110, 1219, 2935, 394, 885, 2444, 2154, 1175
};
- private static final short[] montZetasForVectorNttMultArr = new short[]{
+
+ private static final short[] montZetasForVectorNttMultArr = new short[] {
-1103, 1103, 430, -430, 555, -555, 843, -843,
-1251, 1251, 871, -871, 1550, -1550, 105, -105,
422, -422, 587, -587, 177, -177, -235, 235,
@@ -1174,17 +1162,20 @@ public final class ML_KEM {
}
static void implKyberNttMultJava(short[] result, short[] ntta, short[] nttb) {
- for (int m = 0; m < ML_KEM_N / 2; m++) {
-
- int a0 = ntta[2 * m];
- int a1 = ntta[2 * m + 1];
- int b0 = nttb[2 * m];
- int b1 = nttb[2 * m + 1];
- int r = montMul(a0, b0) +
- montMul(montMul(a1, b1), MONT_ZETAS_FOR_NTT_MULT[m]);
- result[2 * m] = (short) montMul(r, MONT_R_SQUARE_MOD_Q);
- result[2 * m + 1] = (short) montMul(
- (montMul(a0, b1) + montMul(a1, b0)), MONT_R_SQUARE_MOD_Q);
+ for (int m = 0; m < ML_KEM_N; m += 2) {
+ int a0 = ntta[m];
+ int a1 = ntta[m + 1];
+ int b0 = nttb[m];
+ int b1 = nttb[m + 1];
+ long r = a1 * b1;
+ r -= ((r * BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q;
+ r *= ZETAS_FOR_NTT_MULT[m >> 1];
+ r += a0 * b0;
+ result[m] = (short) (r - (((r + BARRETT_ADDEND) *
+ BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q);
+ long r1 = a0 * b1 + a1 * b0;
+ result[m + 1] = (short) (r1 - (((r1 + BARRETT_ADDEND) *
+ BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q);
}
}
@@ -1552,9 +1543,10 @@ public final class ML_KEM {
}
static void implKyberBarrettReduceJava(short[] poly) {
+ int tmp = 0;
for (int m = 0; m < ML_KEM_N; m++) {
- int tmp = ((int) poly[m] * BARRETT_MULTIPLIER) >> BARRETT_SHIFT;
- poly[m] = (short) (poly[m] - tmp * ML_KEM_Q);
+ tmp = poly[m];
+ poly[m] = (short) (tmp - ((tmp * BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q);
}
}
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 5a2b7e6297e..205ec2ab05c 100644
--- a/src/java.base/share/classes/java/lang/classfile/ClassFile.java
+++ b/src/java.base/share/classes/java/lang/classfile/ClassFile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -1046,6 +1046,14 @@ public sealed interface ClassFile
*/
int JAVA_27_VERSION = 71;
+ /**
+ * The class major version introduced by Java SE 28, {@value}.
+ *
+ * @see ClassFileFormatVersion#RELEASE_28
+ * @since 28
+ */
+ int JAVA_28_VERSION = 72;
+
/**
* A minor version number {@value} indicating a class uses preview features
* of a Java SE release since 12, for major versions {@value
@@ -1057,7 +1065,7 @@ public sealed interface ClassFile
* {@return the latest class major version supported by the current runtime}
*/
static int latestMajorVersion() {
- return JAVA_27_VERSION;
+ return JAVA_28_VERSION;
}
/**
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 1990a467b60..52a14bc5125 100644
--- a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java
+++ b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -395,6 +395,18 @@ public enum ClassFileFormatVersion {
* The Java Virtual Machine Specification, Java SE 27 Edition
*/
RELEASE_27(71),
+
+ /**
+ * The version introduced by the Java Platform, Standard Edition
+ * 28.
+ *
+ * @since 28
+ *
+ * @see
+ * The Java Virtual Machine Specification, Java SE 28 Edition
+ */
+ RELEASE_28(72),
; // Reduce code churn when appending new constants
// Note to maintainers: when adding constants for newer releases,
@@ -410,7 +422,7 @@ public enum ClassFileFormatVersion {
* {@return the latest class file format version}
*/
public static ClassFileFormatVersion latest() {
- return RELEASE_27;
+ return RELEASE_28;
}
/**
diff --git a/src/java.base/share/classes/java/security/AsymmetricKey.java b/src/java.base/share/classes/java/security/AsymmetricKey.java
index d37afe9bfea..2177332d174 100644
--- a/src/java.base/share/classes/java/security/AsymmetricKey.java
+++ b/src/java.base/share/classes/java/security/AsymmetricKey.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -34,7 +34,7 @@ import java.security.spec.AlgorithmParameterSpec;
*
* @since 22
*/
-public non-sealed interface AsymmetricKey extends Key, DEREncodable {
+public non-sealed interface AsymmetricKey extends Key, BinaryEncodable {
/**
* Returns the parameters associated with this key.
* The parameters are optional and may be either
diff --git a/src/java.base/share/classes/java/security/DEREncodable.java b/src/java.base/share/classes/java/security/BinaryEncodable.java
similarity index 79%
rename from src/java.base/share/classes/java/security/DEREncodable.java
rename to src/java.base/share/classes/java/security/BinaryEncodable.java
index 1401336037c..bd5d05ee4ec 100644
--- a/src/java.base/share/classes/java/security/DEREncodable.java
+++ b/src/java.base/share/classes/java/security/BinaryEncodable.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,20 +25,22 @@
package java.security;
-import jdk.internal.javac.PreviewFeature;
-
import javax.crypto.EncryptedPrivateKeyInfo;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
+import jdk.internal.javac.PreviewFeature;
+
/**
* This interface is implemented by security API classes that contain
- * binary-encodable key or certificate material.
- * These APIs or their subclasses typically provide methods to convert
- * their instances to and from byte arrays in the Distinguished
- * Encoding Rules (DER) format.
+ * binary-encodable cryptographic material.
+ *
+ *
This sealed interface may evolve. When using {@code switch}, always include a
+ * {@code default} case rather than relying on the classes specified in the
+ * {@code permits} clause to remain fixed. An exhaustive {@code switch} may
+ * result in a {@link MatchException}.
*
* @see AsymmetricKey
* @see KeyPair
@@ -49,11 +51,11 @@ import java.security.spec.X509EncodedKeySpec;
* @see X509CRL
* @see PEM
*
- * @since 25
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
-public sealed interface DEREncodable permits AsymmetricKey, KeyPair,
+public sealed interface BinaryEncodable permits AsymmetricKey, KeyPair,
PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo,
X509Certificate, X509CRL, PEM {
}
diff --git a/src/java.base/share/classes/java/security/KeyPair.java b/src/java.base/share/classes/java/security/KeyPair.java
index 39c98501fea..0efd134c93c 100644
--- a/src/java.base/share/classes/java/security/KeyPair.java
+++ b/src/java.base/share/classes/java/security/KeyPair.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -37,7 +37,7 @@ package java.security;
* @since 1.1
*/
-public final class KeyPair implements java.io.Serializable, DEREncodable {
+public final class KeyPair implements java.io.Serializable, BinaryEncodable {
@java.io.Serial
private static final long serialVersionUID = -7565189502268009837L;
diff --git a/src/java.base/share/classes/java/security/PEM.java b/src/java.base/share/classes/java/security/PEM.java
index 2068fb707dc..13ee9f107cf 100644
--- a/src/java.base/share/classes/java/security/PEM.java
+++ b/src/java.base/share/classes/java/security/PEM.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,53 +27,43 @@ package java.security;
import jdk.internal.javac.PreviewFeature;
+import jdk.internal.ref.CleanerFactory;
+import sun.security.util.KeyUtil;
import sun.security.util.Pem;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
/**
- * {@code PEM} is a {@link DEREncodable} that represents Privacy-Enhanced
- * Mail (PEM) data by its type and Base64-encoded content.
+ * A {@link BinaryEncodable} representing a Privacy-Enhanced Mail (PEM) structure
+ * composed of a type identifier, Base64-encoded content, and optional
+ * leading data that precedes the PEM header.
*
- *
The {@link PEMDecoder#decode(String)} and
- * {@link PEMDecoder#decode(InputStream)} methods return a {@code PEM} object
- * when the data type cannot be represented by a cryptographic object.
- * If you need access to the leading data of a PEM text, or want to
- * handle the text content directly, use the decoding methods
- * {@link PEMDecoder#decode(String, Class)} or
- * {@link PEMDecoder#decode(InputStream, Class)} with {@code PEM.class} as an
- * argument type.
- *
- *
A {@code PEM} object can be encoded back to its textual format by calling
- * {@link #toString()} or by using the encode methods in {@link PEMEncoder}.
- *
- *
When constructing a {@code PEM} instance, both {@code type} and
- * {@code content} must not be {@code null}.
- *
- *
No validation is performed during instantiation to ensure that
- * {@code type} conforms to RFC 7468 or other legacy formats, that
- * {@code content} is valid Base64 data, or that {@code content} matches the
- * {@code type}.
-
- *
Common {@code type} values include, but are not limited to:
+ *
The {@code type} is the label in the PEM header, following the
+ * {@code BEGIN} keyword and excluding the encapsulation boundaries.
+ * Common {@code type} values include, but are not limited to:
* CERTIFICATE, CERTIFICATE REQUEST, ATTRIBUTE CERTIFICATE, X509 CRL, PKCS7,
* CMS, PRIVATE KEY, ENCRYPTED PRIVATE KEY, and PUBLIC KEY.
*
- *
{@code leadingData} is {@code null} if there is no data preceding the PEM
- * header during decoding. {@code leadingData} can be useful for reading
- * metadata that accompanies the PEM data. Because the value may represent a large
- * amount of data, it is not defensively copied by the constructor, and the
- * {@link #leadingData()} method does not return a clone. Modification of the
- * passed-in or returned array changes the value stored in this record.
+ *
Instances of this class are returned by {@link PEMDecoder#decode(String)}
+ * and {@link PEMDecoder#decode(InputStream)} when the content cannot be represented
+ * as a cryptographic object. To explicitly retrieve a {@code PEM} instance
+ * with access to the leading data, use {@link PEMDecoder#decode(String, Class)}
+ * or {@link PEMDecoder#decode(InputStream, Class)} with {@code PEM.class} as the
+ * type.
*
- * @param type the type identifier from the PEM header, without PEM syntax
- * labels; for example, for a public key, {@code type} would be
- * "PUBLIC KEY"
- * @param content the Base64-encoded data, excluding the PEM header and footer
- * @param leadingData any non-PEM data that precedes the PEM header during
- * decoding. This value may be {@code null}.
+ *
A {@code PEM} object can be encoded to its textual representation by
+ * invoking {@link #toString()} or by using {@link PEMEncoder}.
+ *
+ *
To construct a {@code PEM} instance, {@code type} and
+ * {@code base64Content} must be non-{@code null}. For constructors that accept
+ * {@code leadingData}, it must also be non-{@code null}.
+ *
+ *
No validation is performed to ensure that the {@code type} conforms to
+ * RFC 7468 or legacy formats, or that the content corresponds to the declared
+ * {@code type}.
*
* @spec https://www.rfc-editor.org/info/rfc7468
* RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures
@@ -84,64 +74,168 @@ import java.util.Objects;
* @since 26
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
-public record PEM(String type, String content, byte[] leadingData)
- implements DEREncodable {
+public final class PEM implements BinaryEncodable {
+
+ private final String type;
+ private final byte[] content;
+ private byte[] leadingData;
/**
- * Creates a {@code PEM} instance with the specified parameters.
+ * Creates a {@code PEM} instance with the specified type, Base64-encoded
+ * content string, and leading data byte array.
*
- * @param type the PEM type identifier
- * @param content the Base64-encoded data, excluding the PEM header and footer
- * @param leadingData any non-PEM data read during the decoding process
- * before the PEM header. This value may be {@code null}.
- * @throws IllegalArgumentException if {@code type} is incorrectly formatted
- * @throws NullPointerException if {@code type} or {@code content} is {@code null}
+ * @param type the PEM type identifier; must not contain PEM encapsulation
+ * syntax
+ * @param base64Content the Base64-encoded content, excluding the PEM header
+ * and footer
+ * @param leadingData data that precedes the PEM header.
+ * This array is defensively copied.
+ *
+ * @throws IllegalArgumentException if {@code type} contains PEM
+ * encapsulation syntax
+ * @throws NullPointerException if any parameter is {@code null}
*/
- public PEM {
- Objects.requireNonNull(type, "\"type\" cannot be null.");
- Objects.requireNonNull(content, "\"content\" cannot be null.");
+ public PEM(String type, String base64Content, byte[] leadingData) {
+ Objects.requireNonNull(base64Content, "base64Content cannot be null");
+ this(type, base64Content.getBytes(StandardCharsets.ISO_8859_1),
+ leadingData);
+ }
- // With no validity checking on `type`, the constructor accept anything
- // including lowercase. The onus is on the caller.
+ /**
+ * Creates a {@code PEM} instance with the specified type and Base64-encoded
+ * content string.
+ *
+ * @param type the PEM type identifier; must not contain PEM encapsulation
+ * syntax
+ * @param base64Content the Base64-encoded content, excluding the PEM header
+ * and footer
+ * @throws IllegalArgumentException if {@code type} contains PEM
+ * encapsulation syntax
+ * @throws NullPointerException if any parameter is {@code null}
+ */
+ public PEM(String type, String base64Content) {
+ Objects.requireNonNull(base64Content, "base64Content cannot be null");
+ this(type, base64Content.getBytes(StandardCharsets.ISO_8859_1));
+ }
+
+ /**
+ * Creates a {@code PEM} instance with the specified type and Base64-encoded
+ * content and leading data as byte arrays.
+ *
+ * @param type the PEM type identifier; must not contain PEM encapsulation
+ * syntax
+ * @param base64Content the Base64-encoded content, excluding the PEM header
+ * and footer. This array is defensively copied.
+ * @param leadingData data that precedes the PEM header.
+ * This array is defensively copied.
+ *
+ * @throws IllegalArgumentException if {@code type} contains PEM
+ * encapsulation syntax
+ * @throws NullPointerException if any parameter is {@code null}
+ *
+ * @since 27
+ */
+ public PEM(String type, byte[] base64Content, byte[] leadingData) {
+ this(type, base64Content);
+ this.leadingData = Objects.requireNonNull(
+ leadingData, "leadingData cannot be null").clone();
+ }
+
+ /**
+ * Creates a {@code PEM} instance with the specified type and Base64-encoded
+ * content byte array.
+ *
+ * @param type the PEM type identifier; must not contain PEM encapsulation
+ * syntax
+ * @param base64Content the Base64-encoded content, excluding the PEM header
+ * and footer. This array is defensively copied.
+ * @throws IllegalArgumentException if {@code type} contains PEM
+ * encapsulation syntax
+ * @throws NullPointerException if any parameter is {@code null}
+ *
+ * @since 27
+ */
+ public PEM(String type, byte[] base64Content) {
+ Objects.requireNonNull(type, "type cannot be null");
+ Objects.requireNonNull(base64Content, "base64Content cannot be null");
+
+ // The `type` is not checked against any specification. The onus is on
+ // the caller. Only minor formatting checks are done
if (type.startsWith("-") || type.startsWith("BEGIN ") ||
type.startsWith("END ")) {
throw new IllegalArgumentException("PEM syntax labels found. " +
"Only the PEM type identifier is allowed.");
}
+
+ content = base64Content.clone();
+ this.type = type;
+ final var c = content;
+ CleanerFactory.cleaner().register(this, () -> KeyUtil.clear(c));
}
/**
- * Creates a {@code PEM} instance with the specified type and content. This
- * constructor sets {@code leadingData} to {@code null}.
+ * Returns the PEM type identifier.
*
- * @param type the PEM type identifier
- * @param content the Base64-encoded data, excluding the PEM header and footer
- * @throws IllegalArgumentException if {@code type} is incorrectly formatted
- * @throws NullPointerException if {@code type} or {@code content} is {@code null}
+ * @return the PEM type identifier
*/
- public PEM(String type, String content) {
- this(type, content, null);
+ public String type() {
+ return type;
}
/**
- * Returns the PEM formatted string containing the {@code type} and
- * Base64-encoded {@code content}. {@code leadingData} is not included.
+ * Returns the leading data that preceded the PEM header in the decoded
+ * input.
*
- * @return the PEM text representation
+ * @return a newly-allocated byte array containing leading data, or
+ * {@code null} if no leading data is present
*/
- @Override
- final public String toString() {
- return Pem.pemEncoded(this);
+ public byte[] leadingData() {
+ return (leadingData != null) ? leadingData.clone() : null;
}
/**
- * Returns a Base64-decoded byte array of {@code content}, using
+ * Returns the Base64-encoded content.
+ *
+ * @return a newly-allocated byte array containing the Base64 content
+ *
+ * @since 27
+ */
+ public byte[] content() {
+ return content.clone();
+ }
+
+ /**
+ * Returns the Base64-decoded content as a byte array, using
* {@link Base64#getMimeDecoder()}.
*
- * @return a decoded byte array
+ * @return a newly-allocated byte array containing the decoded content
* @throws IllegalArgumentException if decoding fails
*/
- final public byte[] decode() {
+ public byte[] decode() {
return Base64.getMimeDecoder().decode(content);
}
+
+ /**
+ * Returns a PEM string representation of this object, using {@code type}
+ * for the header and footer lines and {@code content} for the Base64 body.
+ *
+ * @return the PEM-formatted string
+ */
+ @Override
+ public String toString() {
+ return new String(Pem.pemEncoded(type, content),
+ StandardCharsets.ISO_8859_1);
+ }
+
+ /*
+ * Returns the PEM string representation as a byte array.
+ */
+ byte[] toTextualByteArray() {
+ return Pem.pemEncoded(type, content);
+ }
+
+ // Clear internal content
+ void clear() {
+ KeyUtil.clear(content);
+ }
}
diff --git a/src/java.base/share/classes/java/security/PEMDecoder.java b/src/java.base/share/classes/java/security/PEMDecoder.java
index 7a4b7753876..f5a6a70d0f5 100644
--- a/src/java.base/share/classes/java/security/PEMDecoder.java
+++ b/src/java.base/share/classes/java/security/PEMDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -34,87 +34,85 @@ import sun.security.util.KeyUtil;
import sun.security.util.Pem;
import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.CryptoException;
import javax.crypto.spec.PBEKeySpec;
import java.io.*;
import java.lang.ref.Reference;
import java.nio.charset.StandardCharsets;
import java.security.cert.*;
import java.security.spec.*;
-import java.util.Base64;
import java.util.Objects;
/**
* {@code PEMDecoder} implements a decoder for Privacy-Enhanced Mail (PEM) data.
* PEM is a textual encoding used to store and transfer cryptographic
* objects, such as asymmetric keys, certificates, and certificate revocation
- * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a
- * Base64-encoded binary encoding enclosed by a type-identifying header
+ * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of
+ * Base64-encoded content enclosed by a type-identifying header
* and footer.
*
*
The {@link #decode(String)} and {@link #decode(InputStream)} methods
* return an instance of a class that matches the PEM type and implements
- * {@link DEREncodable}, as follows:
+ * {@link BinaryEncodable}, as follows:
*
- *
CERTIFICATE : {@link X509Certificate}
- *
X509 CRL : {@link X509CRL}
- *
PUBLIC KEY : {@link PublicKey}
- *
PRIVATE KEY : {@link PrivateKey} or {@link KeyPair}
+ *
CERTIFICATE: {@link X509Certificate}
+ *
X509 CRL: {@link X509CRL}
+ *
PUBLIC KEY: {@link PublicKey}
+ *
PRIVATE KEY: {@link PrivateKey} or {@link KeyPair}
* (if the encoding contains a public key)
ENCRYPTED PRIVATE KEY: {@link PrivateKey} or {@link KeyPair}
* (if the encoding contains a public key)
*
*
- *
For {@code PublicKey} and {@code PrivateKey} types, an algorithm-specific
- * subclass is returned if the algorithm is supported. For example, an
- * {@code ECPublicKey} or an {@code ECPrivateKey} for Elliptic Curve keys.
- *
- *
If the PEM type does not have a corresponding class,
- * {@code decode(String)} and {@code decode(InputStream)} will return a
- * {@code PEM} object.
+ *
If the PEM type has no corresponding class, {@code decode(String)} and
+ * {@code decode(InputStream)} will return a {@code PEM} object.
*
*
The {@link #decode(String, Class)} and {@link #decode(InputStream, Class)}
- * methods take a class parameter that specifies the type of {@code DEREncodable}
- * to return. These methods are useful for avoiding casts when the PEM type is
- * known, or when extracting a specific type if there is more than one option.
- * For example, if the PEM contains both a public and private key, specifying
- * {@code PrivateKey.class} returns only the private key.
- * If the class parameter specifies {@code X509EncodedKeySpec.class}, the
- * public key encoding is returned as an instance of {@code X509EncodedKeySpec}
- * class. Any type of PEM data can be decoded into a {@code PEM} object by
- * specifying {@code PEM.class}. If the class parameter does not match the PEM
- * content, a {@code ClassCastException} is thrown.
+ * methods accept a class parameter specifying the desired {@code BinaryEncodable}
+ * type. These methods avoid the need for casting and are useful when multiple
+ * representations are possible. For example, if the PEM contains both public and
+ * private keys, specifying {@code PrivateKey.class} returns only the private key.
+ * If {@code X509EncodedKeySpec.class} is provided, the public key encoding is
+ * returned as a {@code X509EncodedKeySpec}. To retrieve a {@link PEM} object,
+ * use {@code PEM.class}. If the specified class does not
+ * match the PEM content, a {@code ClassCastException} is thrown.
*
*
In addition to the types listed above, these methods support the
- * following PEM types and {@code DEREncodable} classes when specified as
+ * following PEM types and {@code BinaryEncodable} classes when specified as
* parameters:
*
- *
PUBLIC KEY : {@link X509EncodedKeySpec}
- *
PRIVATE KEY : {@link PKCS8EncodedKeySpec}
- *
PRIVATE KEY : {@link PublicKey} (if the encoding contains a public key)
- *
PRIVATE KEY : {@link X509EncodedKeySpec} (if the encoding contains a public key)
+ *
PUBLIC KEY: {@link X509EncodedKeySpec}
+ *
PRIVATE KEY: {@link PKCS8EncodedKeySpec}
+ *
PRIVATE KEY: {@link PublicKey} (if the encoding contains a public key)
+ *
PRIVATE KEY: {@link X509EncodedKeySpec} (if the encoding contains a public key)
*
* When used with a {@code PEMDecoder} instance configured for decryption:
*
ENCRYPTED PRIVATE KEY: {@link PublicKey} (if the encoding contains a public key)
+ *
ENCRYPTED PRIVATE KEY: {@link X509EncodedKeySpec} (if the encoding contains a public key)
*
*
*
A new {@code PEMDecoder} instance is created when configured
- * with {@link #withFactory(Provider)} or {@link #withDecryption(char[])}.
- * The {@link #withFactory(Provider)} method uses the specified provider
- * to produce cryptographic objects from {@link KeyFactory} and
- * {@link CertificateFactory}. The {@link #withDecryption(char[])} method configures the
+ * with {@link #withFactoriesOf(Provider)} or {@link #withDecryption(char[])}.
+ * The {@link #withFactoriesOf(Provider)} method uses the specified provider when
+ * obtaining {@link KeyFactory} and {@link CertificateFactory} instances used
+ * during decoding. The {@link #withDecryption(char[])} method configures the
* decoder to decrypt and decode encrypted private key PEM data using the given
- * password. If decryption fails, an {@link IllegalArgumentException} is thrown.
- * If an encrypted private key PEM is processed by a decoder not configured
- * for decryption, an {@link EncryptedPrivateKeyInfo} object is returned.
- * A {@code PEMDecoder} configured for decryption will decode unencrypted PEM.
+ * password. If decryption fails, a {@link CryptoException} is thrown.
+ * If an encrypted PEM is processed by a decoder not configured
+ * for decryption, an {@link EncryptedPrivateKeyInfo} is returned.
+ * A {@code PEMDecoder} configured for decryption can also decode unencrypted PEM.
+ *
+ *
The {@code BinaryEncodable} interface may evolve. When using a decode method
+ * with {@code switch}, always include a {@code default} case rather than
+ * relying on the classes specified in the permits clause to remain fixed.
+ * An exhaustive {@code switch} may result in a {@link MatchException}.
*
*
This class is immutable and thread-safe.
*
@@ -127,14 +125,13 @@ import java.util.Objects;
*
Example: configure decryption and a factory provider:
* {@snippet lang = java:
* PEMDecoder pd = PEMDecoder.of().withDecryption(password).
- * withFactory(provider);
- * DEREncodable pemData = pd.decode(privKeyPEM);
- * }
+ * withFactoriesOf(provider);
+ * BinaryEncodable pemData = pd.decode(privKeyPEM);
+ *}
*
- * @implNote This implementation decodes RSA PRIVATE KEY as {@code PrivateKey},
- * X509 CERTIFICATE and X.509 CERTIFICATE as {@code X509Certificate},
- * and CRL as {@code X509CRL}. Other implementations may recognize
- * additional PEM types.
+ * @implNote This implementation decodes non-encrypted RSA PRIVATE KEY as {@code PrivateKey},
+ * X509 CERTIFICATE and X.509 CERTIFICATE as {@code X509Certificate}, and CRL as
+ * {@code X509CRL}. Other implementations may recognize additional PEM types.
*
* @see PEMEncoder
* @see PEM
@@ -149,7 +146,6 @@ import java.util.Objects;
*
* @since 25
*/
-
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
public final class PEMDecoder {
private final Provider factory;
@@ -159,24 +155,23 @@ public final class PEMDecoder {
private final static PEMDecoder PEM_DECODER = new PEMDecoder(null, null);
/**
- * Creates an instance with a specific KeyFactory and/or password.
- * @param withFactory KeyFactory provider
- * @param withPassword char[] password for EncryptedPrivateKeyInfo
- * decryption
+ * Creates an instance with a specific provider and/or password.
+ * @param withFactory Key/Certificate factory provider
+ * @param withKeySpec PBEKeySpec for EncryptedPrivateKeyInfo decryption
*/
- private PEMDecoder(Provider withFactory, PBEKeySpec withPassword) {
- keySpec = withPassword;
+ private PEMDecoder(Provider withFactory, PBEKeySpec withKeySpec) {
+ keySpec = withKeySpec;
factory = withFactory;
- if (withPassword != null) {
+ if (withKeySpec != null) {
final var k = this.keySpec;
CleanerFactory.cleaner().register(this, k::clearPassword);
}
}
/**
- * Returns an instance of {@code PEMDecoder}.
+ * Returns the default {@code PEMDecoder} instance.
*
- * @return a {@code PEMDecoder} instance
+ * @return the default {@code PEMDecoder}
*/
public static PEMDecoder of() {
return PEM_DECODER;
@@ -187,23 +182,21 @@ public final class PEMDecoder {
* header and footer and proceed with decoding the base64 for the
* appropriate type.
*/
- private DEREncodable decode(PEM pem) {
- Base64.Decoder decoder = Base64.getMimeDecoder();
-
+ private BinaryEncodable decode(PEM pem) {
try {
return switch (pem.type()) {
case Pem.PUBLIC_KEY -> {
X509EncodedKeySpec spec =
- new X509EncodedKeySpec(decoder.decode(pem.content()));
+ new X509EncodedKeySpec(pem.decode());
yield getKeyFactory(
KeyUtil.getAlgorithm(spec.getEncoded())).
generatePublic(spec);
}
case Pem.PRIVATE_KEY -> {
- DEREncodable d;
+ BinaryEncodable d;
PKCS8Key p8key = null;
PKCS8EncodedKeySpec p8spec = null;
- byte[] encoding = decoder.decode(pem.content());
+ byte[] encoding = pem.decode();
try {
p8key = new PKCS8Key(encoding);
@@ -238,36 +231,37 @@ public final class PEMDecoder {
}
case Pem.ENCRYPTED_PRIVATE_KEY -> {
byte[] p8 = null;
- byte[] encoding = null;
+ var ekpi = new EncryptedPrivateKeyInfo(pem.decode());
+ if (keySpec == null) {
+ yield ekpi;
+ }
try {
- encoding = decoder.decode(pem.content());
- var ekpi = new EncryptedPrivateKeyInfo(encoding);
- if (keySpec == null) {
- yield ekpi;
- }
p8 = Pem.decryptEncoding(ekpi, keySpec);
- yield Pem.toDEREncodable(p8, true, factory);
+ } catch (GeneralSecurityException e) {
+ throw new CryptoException(e);
+ }
+ try {
+ yield Pem.toPKCS8Encodable(p8, factory);
} finally {
Reference.reachabilityFence(this);
- KeyUtil.clear(encoding, p8);
+ KeyUtil.clear(p8);
}
}
case Pem.CERTIFICATE, Pem.X509_CERTIFICATE,
Pem.X_509_CERTIFICATE -> {
CertificateFactory cf = getCertFactory("X509");
yield (X509Certificate) cf.generateCertificate(
- new ByteArrayInputStream(decoder.decode(pem.content())));
+ new ByteArrayInputStream(pem.decode()));
}
case Pem.X509_CRL, Pem.CRL -> {
CertificateFactory cf = getCertFactory("X509");
yield (X509CRL) cf.generateCRL(
- new ByteArrayInputStream(decoder.decode(pem.content())));
+ new ByteArrayInputStream(pem.decode()));
}
case Pem.RSA_PRIVATE_KEY -> {
KeyFactory kf = getKeyFactory("RSA");
yield kf.generatePrivate(
- RSAPrivateCrtKeyImpl.getKeySpec(decoder.decode(
- pem.content())));
+ RSAPrivateCrtKeyImpl.getKeySpec(pem.decode()));
}
default -> pem;
};
@@ -277,155 +271,182 @@ public final class PEMDecoder {
}
/**
- * Decodes and returns a {@code DEREncodable} from the given {@code String}.
+ * Decodes and returns a {@code BinaryEncodable} from the given {@code String}.
*
*
This method reads the {@code String} until PEM data is found
- * or the end of the {@code String} is reached. If no PEM data is found,
+ * or the end of the {@code String} is reached. If no PEM data is found,
* an {@code IllegalArgumentException} is thrown.
*
- *
A {@code DEREncodable} will be returned that best represents the
- * decoded data. If the PEM type is not supported, a {@code PEM} object is
+ *
A {@code BinaryEncodable} is returned that best represents the
+ * decoded content. If the PEM type is not supported, a {@code PEM} object is
* returned containing the type identifier, Base64-encoded data, and any
- * leading data preceding the PEM header. For {@code DEREncodable} types
- * other than {@code PEM}, leading data is ignored and not returned as part
- * of the {@code DEREncodable} object.
+ * leading data preceding the PEM header. For {@code BinaryEncodable} types
+ * other than {@code PEM}, leading data is ignored.
*
- *
Input consumed by this method is read in as
+ *
The input is interpreted as
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
*
* @param str a {@code String} containing PEM data
- * @return a {@code DEREncodable}
- * @throws IllegalArgumentException on error in decoding or no PEM data found
- * @throws NullPointerException when {@code str} is {@code null}
+ * @return a {@code BinaryEncodable}
+ * @throws IllegalArgumentException if decoding fails or no PEM data is found
+ * @throws NullPointerException if {@code str} is {@code null}
+ * @throws CryptoException if an error occurs during decryption
+ *
+ * @since 27
*/
- public DEREncodable decode(String str) {
+ public BinaryEncodable decode(String str) {
Objects.requireNonNull(str);
+ byte[] encoding = null;
try {
- return decode(new ByteArrayInputStream(
- str.getBytes(StandardCharsets.UTF_8)));
+ encoding = str.getBytes(StandardCharsets.UTF_8);
+ return decode(new ByteArrayInputStream(encoding));
} catch (IOException e) {
// With all data contained in the String, there are no IO ops.
throw new IllegalArgumentException(e);
+ } finally {
+ KeyUtil.clear(encoding);
}
}
/**
- * Decodes and returns a {@code DEREncodable} from the given
+ * Decodes and returns a {@code BinaryEncodable} from the given
* {@code InputStream}.
*
*
This method reads from the {@code InputStream} until the end of
* a PEM footer or the end of the stream. If an I/O error occurs,
- * the read position in the stream may become inconsistent.
- * It is recommended to perform no further decoding operations
- * on the {@code InputStream}.
+ * the stream position may become inconsistent. Further decoding
+ * operations on the same {@code InputStream} are not recommended.
*
- *
A {@code DEREncodable} will be returned that best represents the
- * decoded data. If the PEM type is not supported, a {@code PEM} object is
+ *
A {@code BinaryEncodable} is returned that best represents the
+ * decoded content. If the PEM type is not supported, a {@code PEM} object is
* returned containing the type identifier, Base64-encoded data, and any
- * leading data preceding the PEM header. For {@code DEREncodable} types
- * other than {@code PEM}, leading data is ignored and not returned as part
- * of the {@code DEREncodable} object.
+ * leading data preceding the PEM header. For {@code BinaryEncodable} types
+ * other than {@code PEM}, leading data is ignored.
*
*
If no PEM data is found, an {@code EOFException} is thrown.
*
* @param is {@code InputStream} containing PEM data
- * @return a {@code DEREncodable}
- * @throws IOException on IO or PEM syntax error where the
- * {@code InputStream} did not complete decoding
- * @throws EOFException no PEM data found or unexpectedly reached the
- * end of the {@code InputStream}
- * @throws IllegalArgumentException on error in decoding
- * @throws NullPointerException when {@code is} is {@code null}
+ * @return a {@code BinaryEncodable}
+ * @throws IOException if an I/O error occurs or PEM syntax is invalid
+ * @throws EOFException if no PEM data is found or the stream ends unexpectedly
+ * @throws IllegalArgumentException if decoding fails
+ * @throws NullPointerException if {@code InputStream} is {@code null}
+ * @throws CryptoException if an error occurs during decryption
+ *
+ * @since 27
*/
- public DEREncodable decode(InputStream is) throws IOException {
+ public BinaryEncodable decode(InputStream is) throws IOException {
Objects.requireNonNull(is);
PEM pem = Pem.readPEM(is);
- return decode(pem);
- }
-
- /**
- * Decodes and returns a {@code DEREncodable} of the specified class from
- * the given PEM string. {@code tClass} must be an appropriate class for
- * the PEM type.
- *
- *
This method reads the {@code String} until PEM data is found
- * or the end of the {@code String} is reached. If no PEM data is found,
- * an {@code IllegalArgumentException} is thrown.
- *
- *
If the class parameter is {@code PEM.class}, a {@code PEM} object is
- * returned containing the type identifier, Base64-encoded data, and any
- * leading data preceding the PEM header. For {@code DEREncodable} types
- * other than {@code PEM}, leading data is ignored and not returned as part
- * of the {@code DEREncodable} object.
- *
- *
Input consumed by this method is read in as
- * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
- *
- * @param class type parameter that extends {@code DEREncodable}
- * @param str the {@code String} containing PEM data
- * @param tClass the returned object class that extends or implements
- * {@code DEREncodable}
- * @return a {@code DEREncodable} specified by {@code tClass}
- * @throws IllegalArgumentException on error in decoding or no PEM data found
- * @throws ClassCastException if {@code tClass} does not represent the PEM type
- * @throws NullPointerException when any input values are {@code null}
- */
- public S decode(String str, Class tClass) {
- Objects.requireNonNull(str);
+ BinaryEncodable be = null;
try {
- return decode(new ByteArrayInputStream(
- str.getBytes(StandardCharsets.UTF_8)), tClass);
- } catch (IOException e) {
- // With all data contained in the String, there are no IO ops.
- throw new IllegalArgumentException(e);
+ be = decode(pem);
+ return be;
+ } finally {
+ if (be != pem) {
+ pem.clear();
+ }
}
}
/**
- * Decodes and returns a {@code DEREncodable} of the specified class for the
- * given {@code InputStream}. {@code tClass} must be an appropriate class
- * for the PEM type.
+ * Decodes and returns a {@code BinaryEncodable} of the specified class from
+ * the given PEM string.
+ *
+ *
{@code tClass} must be an appropriate class for the PEM type.
+ *
+ *
This method reads the {@code String} until PEM data is found or the end
+ * of the {@code String} is reached. If no PEM data is found, an
+ * {@code IllegalArgumentException} is thrown.
+ *
+ *
If {@code tClass} is {@code PEM.class}, a {@code PEM} object is returned
+ * containing the type identifier, Base64-encoded data, and any leading data
+ * preceding the PEM header. For {@code BinaryEncodable} types other than
+ * {@code PEM}, leading data is ignored.
+ *
+ *
The input is interpreted as
+ * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}.
+ *
+ * @param class type parameter that extends {@code BinaryEncodable}
+ * @param str the {@code String} containing PEM data
+ * @param tClass the returned object class that extends or implements
+ * {@code BinaryEncodable}
+ * @return a {@code BinaryEncodable} specified by {@code tClass}
+ * @throws IllegalArgumentException on error in decoding or no PEM data found
+ * @throws ClassCastException if {@code tClass} does not represent the PEM type
+ * @throws NullPointerException if any input values are {@code null}
+ * @throws CryptoException if an error occurs during decryption
+ *
+ * @since 27
+ */
+ public S decode(String str, Class tClass) {
+ Objects.requireNonNull(str);
+ byte[] encoding = null;
+ try {
+ encoding = str.getBytes(StandardCharsets.UTF_8);
+ return decode(new ByteArrayInputStream(encoding), tClass);
+ } catch (IOException e) {
+ // With all data contained in the String, there are no IO ops.
+ throw new IllegalArgumentException(e);
+ } finally {
+ KeyUtil.clear(encoding);
+ }
+ }
+
+ /**
+ * Decodes and returns a {@code BinaryEncodable} of the specified class from
+ * the given {@code InputStream}.
+ *
+ *
{@code tClass} must be an appropriate class for the PEM type.
*
*
This method reads from the {@code InputStream} until the end of
* a PEM footer or the end of the stream. If an I/O error occurs,
- * the read position in the stream may become inconsistent.
- * It is recommended to perform no further decoding operations
- * on the {@code InputStream}.
+ * the stream position may become inconsistent. Further decoding
+ * operations on the same {@code InputStream} are not recommended.
*
- *
If the class parameter is {@code PEM.class}, a {@code PEM} object is
- * returned containing the type identifier, Base64-encoded data, and any
- * leading data preceding the PEM header. For {@code DEREncodable} types
- * other than {@code PEM}, leading data is ignored and not returned as part
- * of the {@code DEREncodable} object.
+ *
If {@code tClass} is {@code PEM.class}, a {@code PEM} object is returned
+ * containing the type identifier, Base64-encoded data, and any leading data
+ * preceding the PEM header. For {@code BinaryEncodable} types other than
+ * {@code PEM}, leading data is ignored.
*
*
If no PEM data is found, an {@code EOFException} is thrown.
*
- * @param class type parameter that extends {@code DEREncodable}
+ * @param class type parameter that extends {@code BinaryEncodable}
* @param is an {@code InputStream} containing PEM data
* @param tClass the returned object class that extends or implements
- * {@code DEREncodable}
- * @return a {@code DEREncodable} typecast to {@code tClass}
- * @throws IOException on IO or PEM syntax error where the
- * {@code InputStream} did not complete decoding
- * @throws EOFException no PEM data found or unexpectedly reached the
- * end of the {@code InputStream}
- * @throws IllegalArgumentException on error in decoding
+ * {@code BinaryEncodable}
+ * @return a {@code BinaryEncodable} of type {@code tClass}
+ * @throws IOException if an I/O error occurs or PEM syntax is invalid
+ * @throws EOFException if no PEM data is found or the stream ends unexpectedly
+ * @throws IllegalArgumentException if decoding fails
* @throws ClassCastException if {@code tClass} does not represent the PEM type
- * @throws NullPointerException when any input values are {@code null}
+ * @throws NullPointerException if any input values are {@code null}
+ * @throws CryptoException if an error occurs during decryption
*
- * @see #decode(InputStream)
+ * @see #decode(InputStream)
* @see #decode(String, Class)
+ *
+ * @since 27
*/
- public S decode(InputStream is, Class tClass)
+ public S decode(InputStream is, Class tClass)
throws IOException {
Objects.requireNonNull(is);
Objects.requireNonNull(tClass);
PEM pem = Pem.readPEM(is);
- if (tClass.isAssignableFrom(PEM.class)) {
+ if (tClass == PEM.class) {
return tClass.cast(pem);
+ } else if (tClass == BinaryEncodable.class) {
+ pem.clear();
+ throw new ClassCastException("BinaryEncodable is not a PEM type");
+ }
+
+ BinaryEncodable so;
+ try {
+ so = decode(pem);
+ } finally {
+ pem.clear();
}
- DEREncodable so = decode(pem);
/*
* If the object is a KeyPair, check if the tClass is set to class
@@ -441,6 +462,9 @@ public final class PEMDecoder {
if ((PublicKey.class).isAssignableFrom(tClass) ||
(X509EncodedKeySpec.class).isAssignableFrom(tClass)) {
so = kp.getPublic();
+ if (kp.getPrivate() instanceof PKCS8Key p8Key) {
+ KeyUtil.clear(p8Key);
+ }
}
}
@@ -470,6 +494,10 @@ public final class PEMDecoder {
throw new ClassCastException("Invalid KeySpec " +
"specified: " + tClass.getName() + " for key " +
key.getClass().getName());
+ } finally {
+ if (key instanceof PKCS8Key p8Key) {
+ KeyUtil.clear(p8Key);
+ }
}
}
@@ -509,19 +537,29 @@ public final class PEMDecoder {
* from the specified {@code Provider} to produce cryptographic objects.
* Any errors using the {@code Provider} will occur during decoding.
*
- * @param provider the factory provider
+ * @param provider the factory {@code Provider}
* @return a new {@code PEMDecoder} instance configured with the {@code Provider}
* @throws NullPointerException if {@code provider} is {@code null}
+ *
+ * @since 27
*/
- public PEMDecoder withFactory(Provider provider) {
+ public PEMDecoder withFactoriesOf(Provider provider) {
Objects.requireNonNull(provider);
- return new PEMDecoder(provider, keySpec);
+ if (keySpec == null) {
+ return new PEMDecoder(provider, null);
+ }
+ char[] passwd = keySpec.getPassword();
+ try {
+ return new PEMDecoder(provider, new PBEKeySpec(passwd));
+ } finally {
+ KeyUtil.clear(passwd);
+ }
}
/**
* Returns a copy of this {@code PEMDecoder} that decodes and decrypts
* encrypted private keys using the specified password.
- * Non-encrypted PEM can also be decoded from this instance.
+ * Unencrypted PEM can also be decoded by the returned instance.
*
* @param password the password to decrypt the encrypted PEM data. This array
* is cloned and stored in the new instance.
diff --git a/src/java.base/share/classes/java/security/PEMEncoder.java b/src/java.base/share/classes/java/security/PEMEncoder.java
index 62a0942caf7..211b47008a5 100644
--- a/src/java.base/share/classes/java/security/PEMEncoder.java
+++ b/src/java.base/share/classes/java/security/PEMEncoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,8 @@
package java.security;
import jdk.internal.javac.PreviewFeature;
+
+import jdk.internal.ref.CleanerFactory;
import sun.security.pkcs.PKCS8Key;
import sun.security.util.KeyUtil;
import sun.security.util.Pem;
@@ -35,7 +37,6 @@ import javax.crypto.spec.PBEKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.cert.*;
-import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Objects;
@@ -44,22 +45,19 @@ import java.util.Objects;
* {@code PEMEncoder} implements an encoder for Privacy-Enhanced Mail (PEM)
* data. PEM is a textual encoding used to store and transfer cryptographic
* objects, such as asymmetric keys, certificates, and certificate revocation
- * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a
- * Base64-encoded binary encoding enclosed by a type-identifying header
+ * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a
+ * Base64-encoded content enclosed by a type-identifying header
* and footer.
*
*
Encoding can be performed on cryptographic objects that
- * implement {@link DEREncodable}. The {@link #encode(DEREncodable)}
- * and {@link #encodeToString(DEREncodable)} methods encode a {@code DEREncodable}
+ * implement {@link BinaryEncodable}. The {@link #encode(BinaryEncodable)}
+ * and {@link #encodeToString(BinaryEncodable)} methods encode a {@code BinaryEncodable}
* into PEM and return the data in a byte array or {@code String}.
*
*
Private keys can be encrypted and encoded by configuring a
* {@code PEMEncoder} with the {@link #withEncryption(char[])} method,
* which takes a password and returns a new {@code PEMEncoder} instance
- * configured to encrypt the key with that password. Alternatively, a
- * private key encrypted as an {@link EncryptedPrivateKeyInfo} object can be encoded
- * directly to PEM by passing it to the {@code encode} or
- * {@code encodeToString} methods.
+ * configured to encrypt the key with that password.
*
*
PKCS #8 v2.0 defines the ASN.1 OneAsymmetricKey structure, which may
* contain both private and public keys.
@@ -72,24 +70,24 @@ import java.util.Objects;
* {@link PEM#type()}. The value returned by {@link PEM#leadingData()} is not
* included in the output.
*
- *
The following lists the supported {@code DEREncodable} classes and
- * the PEM types they encode as:
+ *
The following lists the supported {@code BinaryEncodable} classes and
+ * the PEM types they encode to:
*
This class is immutable and thread-safe.
@@ -108,7 +106,6 @@ import java.util.Objects;
*
* @implNote Implementations may support additional PEM types.
*
- *
* @see PEMDecoder
* @see PEM
* @see EncryptedPrivateKeyInfo
@@ -128,21 +125,24 @@ public final class PEMEncoder {
// Singleton instance of PEMEncoder
private static final PEMEncoder PEM_ENCODER = new PEMEncoder(null);
// PBE key for encryption
- private final Key key;
+ private final SecretKey key;
/**
- * Create an encrypted {@code PEMEncoder} instance.
+ * Creates a PEMEncoder instance configured for the given keySpec.
*/
private PEMEncoder(PBEKeySpec keySpec) {
if (keySpec != null) {
try {
key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO).
generateSecret(keySpec);
+ final SecretKey k = this.key;
+ CleanerFactory.cleaner().register(this,
+ () -> KeyUtil.destroySecretKeys(k));
} catch (GeneralSecurityException e) {
- throw new IllegalArgumentException("Operation failed: " +
+ throw new CryptoException("Operation failed: " +
"unable to generate key or locate a valid algorithm. " +
"Check the jdk.epkcs8.defaultAlgorithm security " +
- "property for a valid configuration.", e);
+ "property for a valid configuration", e);
}
} else {
key = null;
@@ -159,63 +159,85 @@ public final class PEMEncoder {
}
/**
- * Encodes the specified {@code DEREncodable} and returns a PEM-encoded
+ * Encodes the specified {@code BinaryEncodable} and returns a PEM-encoded
* string.
*
- * @param de the {@code DEREncodable} to be encoded
+ * @param be the {@code BinaryEncodable} to encode
* @return a {@code String} containing the PEM-encoded data
- * @throws IllegalArgumentException if the {@code DEREncodable} cannot be encoded
- * @throws NullPointerException if {@code de} is {@code null}
+ * @throws IllegalArgumentException if {@code be} has no encoding, is
+ * an unsupported class, or cannot be used with encryption
+ * @throws NullPointerException if {@code be} is {@code null}
+ * @throws CryptoException if an error occurs during encryption
* @see #withEncryption(char[])
+ *
+ * @since 27
*/
- public String encodeToString(DEREncodable de) {
- Objects.requireNonNull(de);
- return switch (de) {
- case PublicKey pu -> buildKey(pu.getEncoded(), null);
- case PrivateKey pr -> {
- byte[] encoding = pr.getEncoded();
- try {
- yield buildKey(null, encoding);
- } finally {
- KeyUtil.clear(encoding);
- }
+ public String encodeToString(BinaryEncodable be) {
+ Objects.requireNonNull(be);
+ if (be instanceof PEM pem) {
+ if (key != null) {
+ throw new IllegalArgumentException("PEM cannot be " +
+ "encrypted");
}
+ return pem.toString();
+ }
+ return KeyUtil.clear(encode(be),
+ e -> new String(e, StandardCharsets.ISO_8859_1));
+ }
+
+ /**
+ * Encodes the specified {@code BinaryEncodable} and returns a PEM-encoded
+ * byte array.
+ *
+ * @param be the {@code BinaryEncodable} to encode
+ * @return a PEM-encoded byte array
+ * @throws IllegalArgumentException if {@code be} has no encoding, is
+ * an unsupported class, or cannot be used with encryption
+ * @throws NullPointerException if {@code be} is {@code null}
+ * @throws CryptoException if an error occurs during encryption
+ * @see #withEncryption(char[])
+ *
+ * @since 27
+ */
+ public byte[] encode(BinaryEncodable be) {
+ return switch (be) {
+ case PublicKey pu -> buildKey(pu.getEncoded(), null);
+ case PrivateKey pr ->
+ KeyUtil.clear(pr.getEncoded(), e -> buildKey(null, e));
case KeyPair kp -> {
- byte[] encoding = null;
- try {
- if (kp.getPublic() == null) {
- throw new IllegalArgumentException("KeyPair does not " +
- "contain PublicKey.");
- }
- if (kp.getPrivate() == null) {
- throw new IllegalArgumentException("KeyPair does not " +
- "contain PrivateKey.");
- }
- encoding = kp.getPrivate().getEncoded();
- if (encoding == null || encoding.length == 0) {
- throw new IllegalArgumentException("PrivateKey is " +
- "null or has no encoding.");
- }
- yield buildKey(kp.getPublic().getEncoded(), encoding);
- } finally {
- KeyUtil.clear(encoding);
+ if (kp.getPublic() == null) {
+ throw new IllegalArgumentException("KeyPair does not " +
+ "contain PublicKey");
}
+ if (kp.getPrivate() == null) {
+ throw new IllegalArgumentException("KeyPair does not " +
+ "contain PrivateKey");
+ }
+ byte[] pubEncoding = kp.getPublic().getEncoded();
+ if (pubEncoding == null || pubEncoding.length == 0) {
+ throw new IllegalArgumentException("PublicKey is " +
+ "null or has no encoding");
+ }
+ byte[] encoding = kp.getPrivate().getEncoded();
+ if (encoding == null || encoding.length == 0) {
+ throw new IllegalArgumentException("PrivateKey is " +
+ "null or has no encoding");
+ }
+ yield KeyUtil.clear(encoding, e -> buildKey(pubEncoding, e));
}
case X509EncodedKeySpec x -> buildKey(x.getEncoded(), null);
- case PKCS8EncodedKeySpec p -> buildKey(null, p.getEncoded());
+ case PKCS8EncodedKeySpec p ->
+ KeyUtil.clear(p.getEncoded(), e -> buildKey(null, e));
case EncryptedPrivateKeyInfo epki -> {
- byte[] encoding = null;
if (key != null) {
throw new IllegalArgumentException(
"EncryptedPrivateKeyInfo cannot be encrypted");
}
try {
- encoding = epki.getEncoded();
- yield Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY, encoding);
+ yield KeyUtil.clear(epki.getEncoded(),
+ e -> Pem.pemEncodedFromDER(Pem.ENCRYPTED_PRIVATE_KEY, e));
} catch (IOException e) {
throw new IllegalArgumentException(e);
- } finally {
- KeyUtil.clear(encoding);
}
}
case X509Certificate c -> {
@@ -224,7 +246,7 @@ public final class PEMEncoder {
"cannot be encrypted");
}
try {
- yield Pem.pemEncoded(Pem.CERTIFICATE, c.getEncoded());
+ yield Pem.pemEncodedFromDER(Pem.CERTIFICATE, c.getEncoded());
} catch (CertificateEncodingException e) {
throw new IllegalArgumentException(e);
}
@@ -235,7 +257,7 @@ public final class PEMEncoder {
"encrypted");
}
try {
- yield Pem.pemEncoded(Pem.X509_CRL, crl.getEncoded());
+ yield Pem.pemEncodedFromDER(Pem.X509_CRL, crl.getEncoded());
} catch (CRLException e) {
throw new IllegalArgumentException(e);
}
@@ -245,53 +267,39 @@ public final class PEMEncoder {
throw new IllegalArgumentException("PEM cannot be " +
"encrypted");
}
- yield Pem.pemEncoded(rec);
+ yield rec.toTextualByteArray();
}
default -> throw new IllegalArgumentException("PEM does not " +
- "support " + de.getClass().getCanonicalName());
+ "support " + be.getClass().getCanonicalName());
};
}
/**
- * Encodes the specified {@code DEREncodable} and returns a PEM-encoded
- * byte array.
- *
- * @param de the {@code DEREncodable} to be encoded
- * @return a PEM-encoded byte array
- * @throws IllegalArgumentException if the {@code DEREncodable} cannot be encoded
- * @throws NullPointerException if {@code de} is {@code null}
- * @see #withEncryption(char[])
- */
- public byte[] encode(DEREncodable de) {
- return encodeToString(de).getBytes(StandardCharsets.ISO_8859_1);
- }
-
- /**
- * Returns a copy of this PEMEncoder that encrypts and encodes
- * using the specified password and default encryption algorithm.
+ * Returns a copy of this {@code PEMEncoder} configured to encrypt and
+ * encode using the specified password and the default encryption algorithm.
*
*
Only {@code PrivateKey}, {@code KeyPair}, and
* {@code PKCS8EncodedKeySpec} objects can be encoded with this newly
- * configured instance. Encoding other {@code DEREncodable} objects will
- * throw an {@code IllegalArgumentException}.
+ * configured instance. Attempting to encode other {@code BinaryEncodable}
+ * objects will throw an {@code IllegalArgumentException}.
+ *
+ *
To use non-default encryption parameters or a different provider, use
+ * an {@code encrypt} method in {@link EncryptedPrivateKeyInfo}, then pass
+ * the resulting object to {@link #encode(BinaryEncodable)}.
*
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} security property
* defines the default encryption algorithm. The {@code AlgorithmParameterSpec}
- * defaults are determined by the provider. To use non-default encryption
- * parameters, or to encrypt with a different encryption provider, use
- * {@link EncryptedPrivateKeyInfo#encrypt(DEREncodable, Key,
- * String, AlgorithmParameterSpec, Provider, SecureRandom)} and use the
- * returned object with {@link #encode(DEREncodable)}.
+ * defaults are determined by the provider.
*
* @param password the encryption password. The array is cloned and
* stored in the new instance.
* @return a new {@code PEMEncoder} instance configured for encryption
- * @throws NullPointerException if password is {@code null}
- * @throws IllegalArgumentException if generating the encryption key fails
+ * @throws NullPointerException if {@code password} is {@code null}
+ * @throws CryptoException if generating the encryption key fails
*/
public PEMEncoder withEncryption(char[] password) {
- Objects.requireNonNull(password, "password cannot be null.");
+ Objects.requireNonNull(password, "password cannot be null");
PBEKeySpec keySpec = new PBEKeySpec(password);
try {
return new PEMEncoder(keySpec);
@@ -301,14 +309,12 @@ public final class PEMEncoder {
}
/**
- * Build PEM encoding.
- *
- * privateKeyEncoding will be zeroed when the method returns
+ * Build the PEM encoding for AsymmetricKey and KeyPair
*/
- private String buildKey(byte[] publicEncoding, byte[] privateEncoding) {
+ private byte[] buildKey(byte[] publicEncoding, byte[] privateEncoding) {
if (publicEncoding == null && privateEncoding == null) {
throw new IllegalArgumentException("No encoded data given by the " +
- "DEREncodable.");
+ "BinaryEncodable");
}
if (publicEncoding != null && publicEncoding.length == 0) {
@@ -322,35 +328,36 @@ public final class PEMEncoder {
}
if (key != null && privateEncoding == null) {
- throw new IllegalArgumentException("This DEREncodable cannot " +
- "be encrypted.");
+ throw new IllegalArgumentException("This BinaryEncodable cannot " +
+ "be encrypted");
}
// X509 only
if (publicEncoding != null && privateEncoding == null) {
- return Pem.pemEncoded(Pem.PUBLIC_KEY, publicEncoding);
+ return Pem.pemEncodedFromDER(Pem.PUBLIC_KEY, publicEncoding);
}
byte[] encoding = null;
PKCS8EncodedKeySpec p8KeySpec = null;
try {
if (publicEncoding == null) {
- encoding = privateEncoding;
+ encoding = privateEncoding.clone();
} else {
encoding = PKCS8Key.getEncoded(publicEncoding,
privateEncoding);
}
if (key != null) {
p8KeySpec = new PKCS8EncodedKeySpec(encoding);
+ KeyUtil.clear(encoding);
encoding = EncryptedPrivateKeyInfo.encrypt(p8KeySpec, key,
Pem.DEFAULT_ALGO, null, null, null).
getEncoded();
}
if (encoding.length == 0) {
throw new IllegalArgumentException("No private key encoding " +
- "given by the DEREncodable.");
+ "given by the BinaryEncodable");
}
- return Pem.pemEncoded(
+ return Pem.pemEncodedFromDER(
(key == null ? Pem.PRIVATE_KEY : Pem.ENCRYPTED_PRIVATE_KEY),
encoding);
} catch (IOException e) {
diff --git a/src/java.base/share/classes/java/security/cert/X509CRL.java b/src/java.base/share/classes/java/security/cert/X509CRL.java
index d19618f81ed..2745f0e377b 100644
--- a/src/java.base/share/classes/java/security/cert/X509CRL.java
+++ b/src/java.base/share/classes/java/security/cert/X509CRL.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -107,7 +107,7 @@ import java.util.Set;
* @see X509Extension
*/
-public abstract non-sealed class X509CRL extends CRL implements X509Extension, DEREncodable {
+public abstract non-sealed class X509CRL extends CRL implements X509Extension, BinaryEncodable {
private transient X500Principal issuerPrincipal;
diff --git a/src/java.base/share/classes/java/security/cert/X509Certificate.java b/src/java.base/share/classes/java/security/cert/X509Certificate.java
index fe4a472dead..bd19f3d33d0 100644
--- a/src/java.base/share/classes/java/security/cert/X509Certificate.java
+++ b/src/java.base/share/classes/java/security/cert/X509Certificate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -108,7 +108,7 @@ import java.util.List;
*/
public abstract non-sealed class X509Certificate extends Certificate
- implements X509Extension, DEREncodable {
+ implements X509Extension, BinaryEncodable {
@java.io.Serial
private static final long serialVersionUID = -2491127588187038216L;
diff --git a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java
index 0518e6ed272..319f1ad4cc8 100644
--- a/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java
+++ b/src/java.base/share/classes/java/security/spec/PKCS8EncodedKeySpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,7 @@
package java.security.spec;
-import java.security.DEREncodable;
+import java.security.BinaryEncodable;
/**
* This class represents the ASN.1 encoding of a private key,
@@ -73,7 +73,7 @@ import java.security.DEREncodable;
*/
public non-sealed class PKCS8EncodedKeySpec extends EncodedKeySpec implements
- DEREncodable {
+ BinaryEncodable {
/**
* Creates a new {@code PKCS8EncodedKeySpec} with the given encoded key.
*
diff --git a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java
index 6d0f105e64f..875660a8ed4 100644
--- a/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java
+++ b/src/java.base/share/classes/java/security/spec/X509EncodedKeySpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,7 @@
package java.security.spec;
-import java.security.DEREncodable;
+import java.security.BinaryEncodable;
/**
* This class represents the ASN.1 encoding of a public key,
@@ -52,7 +52,7 @@ import java.security.DEREncodable;
*/
public non-sealed class X509EncodedKeySpec extends EncodedKeySpec implements
- DEREncodable {
+ BinaryEncodable {
/**
* Creates a new {@code X509EncodedKeySpec} with the given encoded key.
*
diff --git a/src/java.base/share/classes/java/text/ListFormat.java b/src/java.base/share/classes/java/text/ListFormat.java
index 5cd0e9e3651..c610ace64d6 100644
--- a/src/java.base/share/classes/java/text/ListFormat.java
+++ b/src/java.base/share/classes/java/text/ListFormat.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -32,7 +32,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
-import java.util.regex.Pattern;
import java.util.stream.IntStream;
import sun.util.locale.provider.LocaleProviderAdapter;
@@ -85,9 +84,9 @@ import sun.util.locale.provider.LocaleProviderAdapter;
* Note: these examples are from CLDR, there could be different results from other locale providers.
*
* Alternatively, Locale, Type, and/or Style independent instances
- * can be created with {@link #getInstance(String[])}. The String array to the
- * method specifies the delimiting patterns for the start/middle/end portion of
- * the formatted string, as well as optional specialized patterns for two or three
+ * can be created with {@link #getInstance(String[])}. The String array passed to the
+ * method specifies the delimiting patterns for the {@code start}/{@code middle}/{@code end}
+ * portion of the formatted string, as well as optional specialized patterns for two or three
* elements. Refer to the method description for more detail.
*
* On parsing, if some ambiguity is found in the input string, such as delimiting
@@ -112,6 +111,7 @@ public final class ListFormat extends Format {
private static final int TWO = 3;
private static final int THREE = 4;
private static final int PATTERN_ARRAY_LENGTH = THREE + 1;
+ private static final int PLACEHOLDER_LENGTH = 3; // i.e., "{i}".length()
/**
* The locale to use for formatting list patterns.
@@ -121,19 +121,17 @@ public final class ListFormat extends Format {
/**
* The array of five pattern Strings. Each element corresponds to the Unicode LDML's
- * `listPatternsPart` type, i.e, start/middle/end/two/three.
+ * {@code listPatternPart} type, i.e,
+ * {@code start}/{@code middle}/{@code end}/{@code two}/{@code three}.
* @serial
*/
private final String[] patterns;
- private static final Pattern PARSE_START = Pattern.compile("(.*?)\\{0}(.*?)\\{1}");
- private static final Pattern PARSE_MIDDLE = Pattern.compile("\\{0}(.*?)\\{1}");
- private static final Pattern PARSE_END = Pattern.compile("\\{0}(.*?)\\{1}(.*?)");
- private static final Pattern PARSE_TWO = Pattern.compile("(.*?)\\{0}(.*?)\\{1}(.*?)");
- private static final Pattern PARSE_THREE = Pattern.compile("(.*?)\\{0}(.*?)\\{1}(.*?)\\{2}(.*?)");
- private transient Pattern startPattern;
+ private transient String startBefore;
+ private transient String startBetween;
private transient String middleBetween;
- private transient Pattern endPattern;
+ private transient String endBetween;
+ private transient String endAfter;
private ListFormat(Locale l, String[] patterns) {
locale = l;
@@ -149,50 +147,65 @@ public final class ListFormat extends Format {
}
}
- // get pattern strings
- var m = PARSE_START.matcher(patterns[START]);
- String startBefore;
- String startBetween;
- if (m.matches()) {
- startBefore = m.group(1);
- startBetween = m.group(2);
+ // Get pattern strings. Pattern conditions from LDML are:
+ // - it contains the placeholders {0}, {1}, and {2} ("3"-pattern only) in order
+ // - "start" and "middle" patterns end with the {1} placeholder
+ // - "middle" and "end" patterns begin with the {0} placeholder
+ var pattern = patterns[START];
+ var placeholderPositions = findPlaceholders(pattern);
+ if (placeholderPositions != null &&
+ placeholderPositions[2] == -1 &&
+ placeholderPositions[1] + PLACEHOLDER_LENGTH == pattern.length()) {
+ startBefore = pattern.substring(0, placeholderPositions[0]);
+ startBetween = pattern.substring(placeholderPositions[0] + PLACEHOLDER_LENGTH,
+ placeholderPositions[1]);
} else {
- throw new IllegalArgumentException("start pattern is incorrect: " + patterns[START]);
+ throw new IllegalArgumentException("start pattern is incorrect: " + pattern);
}
- m = PARSE_MIDDLE.matcher(patterns[MIDDLE]);
- if (m.matches()) {
- middleBetween = m.group(1);
+
+ pattern = patterns[MIDDLE];
+ placeholderPositions = findPlaceholders(pattern);
+ if (placeholderPositions != null &&
+ placeholderPositions[2] == -1 &&
+ placeholderPositions[0] == 0 &&
+ placeholderPositions[1] + PLACEHOLDER_LENGTH == pattern.length()) {
+ middleBetween = pattern.substring(placeholderPositions[0] + PLACEHOLDER_LENGTH,
+ placeholderPositions[1]);
} else {
- throw new IllegalArgumentException("middle pattern is incorrect: " + patterns[MIDDLE]);
+ throw new IllegalArgumentException("middle pattern is incorrect: " + pattern);
}
- m = PARSE_END.matcher(patterns[END]);
- String endBetween;
- String endAfter;
- if (m.matches()) {
- endBetween = m.group(1);
- endAfter = m.group(2);
+
+ pattern = patterns[END];
+ placeholderPositions = findPlaceholders(pattern);
+ if (placeholderPositions != null &&
+ placeholderPositions[2] == -1 &&
+ placeholderPositions[0] == 0) {
+ endBetween = pattern.substring(placeholderPositions[0] + PLACEHOLDER_LENGTH,
+ placeholderPositions[1]);
+ endAfter = pattern.substring(placeholderPositions[1] + PLACEHOLDER_LENGTH);
} else {
- throw new IllegalArgumentException("end pattern is incorrect: " + patterns[END]);
+ throw new IllegalArgumentException("end pattern is incorrect: " + pattern);
}
// Validate two/three patterns, if given. Otherwise, generate them
- if (!patterns[TWO].isEmpty()) {
- if (!PARSE_TWO.matcher(patterns[TWO]).matches()) {
- throw new IllegalArgumentException("pattern for two is incorrect: " + patterns[TWO]);
+ pattern = patterns[TWO];
+ if (!pattern.isEmpty()) {
+ placeholderPositions = findPlaceholders(pattern);
+ if (placeholderPositions == null || placeholderPositions[2] >= 0) {
+ throw new IllegalArgumentException("pattern for two is incorrect: " + pattern);
}
} else {
patterns[TWO] = startBefore + "{0}" + endBetween + "{1}" + endAfter;
}
- if (!patterns[THREE].isEmpty()) {
- if (!PARSE_THREE.matcher(patterns[THREE]).matches()) {
- throw new IllegalArgumentException("pattern for three is incorrect: " + patterns[THREE]);
+ pattern = patterns[THREE];
+ if (!pattern.isEmpty()) {
+ placeholderPositions = findPlaceholders(pattern);
+ if (placeholderPositions == null || placeholderPositions[2] == -1) {
+ throw new IllegalArgumentException("pattern for three is incorrect: " + pattern);
}
} else {
patterns[THREE] = startBefore + "{0}" + startBetween + "{1}" + endBetween + "{2}" + endAfter;
}
-
- startPattern = Pattern.compile(startBefore + "(.+?)" + startBetween);
- endPattern = Pattern.compile(endBetween + "(.+?)" + endAfter);
}
/**
@@ -238,36 +251,43 @@ public final class ListFormat extends Format {
* instead of letting the runtime provide appropriate patterns for the {@code Locale},
* {@code Type}, or {@code Style}.
*
- * The patterns array should contain five String patterns, each corresponding to the Unicode LDML's
- * {@code listPatternPart}, i.e., "start", "middle", "end", two element, and three element patterns
- * in this order. Each pattern contains "{0}" and "{1}" (and "{2}" for the three element pattern)
- * placeholders that are substituted with the passed input strings on formatting.
- * If the length of the patterns array is not 5, an {@code IllegalArgumentException}
- * is thrown.
+ * The patterns array should contain five String patterns, each corresponding
+ * to the Unicode LDML's {@code listPatternPart}, i.e., {@code start},
+ * {@code middle}, {@code end}, {@code two} element, and {@code three}
+ * element patterns in this order. Each pattern contains "{0}" and "{1}"
+ * (and "{2}" for the {@code three} element pattern) placeholders that are
+ * substituted with the passed input strings on formatting. If the length of
+ * the patterns array is not 5, an {@code IllegalArgumentException} is thrown.
*
* Each pattern string is first parsed as follows. Literals in parentheses, such as
* "start_before", are optional:
- *
+ * {@snippet :
* start := (start_before){0}start_between{1}
* middle := {0}middle_between{1}
* end := {0}end_between{1}(end_after)
* two := (two_before){0}two_between{1}(two_after)
* three := (three_before){0}three_between1{1}three_between2{2}(three_after)
- *
- * If two or three pattern string is empty, it falls back to
- * {@code "(start_before){0}end_between{1}(end_after)"},
- * {@code "(start_before){0}start_between{1}end_between{2}(end_after)"} respectively.
- * If parsing of any pattern string for start, middle, end, two, or three fails,
+ * }
+ * If the {@code two} or {@code three} pattern string is empty, it falls back to
+ * {@snippet :
+ * (start_before){0}end_between{1}(end_after)
+ * (start_before){0}start_between{1}end_between{2}(end_after)
+ * }
+ * respectively.
+ * If parsing of any pattern string for {@code start}, {@code middle},
+ * {@code end}, {@code two}, or {@code three} fails, including duplicate
+ * placeholders, "{2}" in patterns other than the {@code three} element
+ * pattern, or any use of "{" or "}" other than "{0}", "{1}", or "{2}",
* it throws an {@code IllegalArgumentException}.
*
* On formatting, the input string list with {@code n} elements substitutes above
* placeholders based on the number of elements:
- *
+ * {@snippet :
* n = 1: {0}
* n = 2: parsed pattern for "two"
* n = 3: parsed pattern for "three"
* n > 3: (start_before){0}start_between{1}middle_between{2} ... middle_between{m}end_between{n}(end_after)
- *
+ * }
* As an example, the following table shows a pattern array which is equivalent to
* {@code STANDARD} type, {@code FULL} style in US English:
*
@@ -455,30 +475,41 @@ public final class ListFormat extends Format {
public Object parseObject(String source, ParsePosition parsePos) {
Objects.requireNonNull(source);
Objects.requireNonNull(parsePos);
- var sm = startPattern.matcher(source);
- var em = endPattern.matcher(source);
+ var startPattern = findPattern(source, parsePos.getIndex(), startBefore, startBetween);
+ var endPattern = findPattern(source, parsePos.getIndex(), endBetween, endAfter);
Object parsed = null;
- if (sm.find(parsePos.getIndex()) && em.find(parsePos.getIndex())) {
- // get em to the last
- var c = em.start();
- while (em.find()) {
- c = em.start();
+ if (startPattern != null && endPattern != null) {
+ // get endPattern to the last
+ var ep = endPattern;
+ while ((ep = findPattern(source, ep[1], endBetween, endAfter)) != null) {
+ endPattern = ep;
}
- em.find(c);
- var startEnd = sm.end();
- var endStart = em.start();
+
+ var startEnd = startPattern[1];
+ var endStart = endPattern[0];
if (startEnd <= endStart) {
var mid = source.substring(startEnd, endStart);
- var count = mid.split(middleBetween).length + 2;
- parsed = new MessageFormat(createMessageFormatString(count), locale).parseObject(source, parsePos);
+ var count = 3;
+ var mbLength = middleBetween.length();
+ if (mbLength > 0) {
+ var midIndex = 0;
+ while ((midIndex = mid.indexOf(middleBetween, midIndex)) >= 0) {
+ count++;
+ midIndex += mbLength;
+ }
+ }
+ parsed = new MessageFormat(listToMessageFormatPattern(createMessageFormatString(count)),
+ locale).parseObject(source, parsePos);
}
}
if (parsed == null) {
// now try exact number patterns
- parsed = new MessageFormat(patterns[TWO], locale).parseObject(source, parsePos);
+ parsed = new MessageFormat(listToMessageFormatPattern(patterns[TWO]),
+ locale).parseObject(source, parsePos);
if (parsed == null) {
- parsed = new MessageFormat(patterns[THREE], locale).parseObject(source, parsePos);
+ parsed = new MessageFormat(listToMessageFormatPattern(patterns[THREE]),
+ locale).parseObject(source, parsePos);
}
}
@@ -556,16 +587,18 @@ public final class ListFormat extends Format {
var len = input.length;
return switch (len) {
case 0 -> throw new IllegalArgumentException("There should at least be one input string");
- case 1 -> new MessageFormat("{0}", locale);
- case 2, 3 -> new MessageFormat(patterns[len + 1], locale);
- default -> new MessageFormat(createMessageFormatString(len), locale);
+ case 1 -> new MessageFormat(listToMessageFormatPattern("{0}"), locale);
+ case 2, 3 -> new MessageFormat(listToMessageFormatPattern(patterns[len + 1]), locale);
+ default -> new MessageFormat(listToMessageFormatPattern(createMessageFormatString(len)), locale);
};
}
private String createMessageFormatString(int count) {
var sb = new StringBuilder(256).append(patterns[START]);
IntStream.range(2, count - 1).forEach(i -> sb.append(middleBetween).append("{").append(i).append("}"));
- sb.append(patterns[END].replaceFirst("\\{0}", "").replaceFirst("\\{1}", "\\{" + (count - 1) + "\\}"));
+ sb.append(endBetween)
+ .append("{").append(count - 1).append("}")
+ .append(endAfter);
return sb.toString();
}
@@ -643,4 +676,87 @@ public final class ListFormat extends Format {
*/
NARROW
}
+
+ /**
+ * {@return the positions of the "{0}", "{1}", and "{2}" placeholders in the
+ * given pattern string, or null if the pattern is invalid}
+ * Only "{0}", "{1}", or "{2}" placeholders are allowed. Any other use of
+ * curly braces is not allowed.
+ *
+ * The returned array contains -1 for "{2}" if that placeholder is absent.
+ *
+ * @param pattern pattern string to parse
+ */
+ private static int[] findPlaceholders(String pattern) {
+ var positions = new int[] {-1, -1, -1};
+
+ for (int i = 0; i < pattern.length(); i++) {
+ var ch = pattern.charAt(i);
+ if (ch == '{') {
+ if (i + PLACEHOLDER_LENGTH > pattern.length() ||
+ pattern.charAt(i + 1) < '0' ||
+ pattern.charAt(i + 1) > '2' ||
+ pattern.charAt(i + 2) != '}') {
+ return null;
+ }
+
+ // Check for duplicate placeholders
+ var index = pattern.charAt(i + 1) - '0';
+ if (positions[index] != -1) {
+ return null;
+ }
+
+ positions[index] = i;
+ i += PLACEHOLDER_LENGTH - 1;
+ } else if (ch == '}') {
+ return null;
+ }
+ }
+
+ // Check the existence and order of the placeholders
+ if (positions[0] == -1 ||
+ positions[1] == -1 ||
+ positions[0] + PLACEHOLDER_LENGTH > positions[1] ||
+ positions[2] != -1 && positions[1] + PLACEHOLDER_LENGTH > positions[2]) {
+ return null;
+ }
+
+ return positions;
+ }
+
+ /**
+ * {@return the start and end positions of the first pattern found in
+ * the given {@code source} starting at {@code pos}, or null if no such
+ * pattern exists}
+ *
+ * The pattern must contain at least one character between the
+ * {@code prefix} and {@code suffix} strings. The returned end position is
+ * exclusive.
+ *
+ * @param source string to search
+ * @param pos position at which to start the search
+ * @param prefix starting string within the pattern
+ * @param suffix ending string within the pattern
+ */
+ private static int[] findPattern(String source, int pos, String prefix, String suffix) {
+ var prefixPos = source.indexOf(prefix, pos);
+ var suffixPos = prefixPos != -1 ? source.indexOf(suffix, prefixPos + prefix.length() + 1) : -1;
+
+ return prefixPos < suffixPos ?
+ new int[] {prefixPos, suffixPos + suffix.length()} : null;
+ }
+
+ /**
+ * {@return the MessageFormat pattern corresponding to the passed ListFormat pattern}
+ *
+ * Single quotes must be escaped so they are interpreted as literal text
+ * as opposed to escaping delimiters when passed to MessageFormat. Everything
+ * else remains the same; ListFormat already handles other validation on its own.
+ *
+ * @param pattern list pattern to use
+ */
+ private static String listToMessageFormatPattern(String pattern) {
+ return pattern.indexOf('\'') < 0 ? pattern :
+ pattern.replace("'", "''");
+ }
}
diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java
index a906215f16b..d9befd9c454 100644
--- a/src/java.base/share/classes/java/util/List.java
+++ b/src/java.base/share/classes/java/util/List.java
@@ -1242,7 +1242,11 @@ public interface List extends SequencedCollection {
* {@linkplain Object#hashCode() hashCode()}, and
* {@linkplain Object#toString() toString()} methods may trigger initialization of
* one or more lazy elements. If initialization fails for at least one element,
- * the {@linkplain Object Object methods} may throw {@linkplain NoSuchElementException}.
+ * the {@linkplain Object#hashCode() hashCode()} and
+ * {@linkplain Object#toString() toString()} methods throw
+ * {@linkplain NoSuchElementException}, and the {@linkplain Object#equals(Object)}
+ * throw {@linkplain NoSuchElementException} if attempting to compare an element that
+ * could not be computed.
*
* The returned lazy list strongly references its computing
* function used to compute elements at least as long as there are uninitialized
diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java
index f5fb190f7d8..11a64295d2b 100644
--- a/src/java.base/share/classes/java/util/Map.java
+++ b/src/java.base/share/classes/java/util/Map.java
@@ -1793,8 +1793,12 @@ public interface Map {
* {@linkplain Object#equals(Object) equals()},
* {@linkplain Object#hashCode() hashCode()}, and
* {@linkplain Object#toString() toString()} methods may trigger initialization of
- * one or more lazy values. If initialization fails for at least one value,
- * the {@linkplain Object Object methods} may throw {@linkplain NoSuchElementException}.
+ * one or more lazy values. If initialization fails for at least one value,
+ * the {@linkplain Object#hashCode() hashCode()} and
+ * {@linkplain Object#toString() toString()} methods throw
+ * {@linkplain NoSuchElementException}, and the {@linkplain Object#equals(Object)}
+ * throw {@linkplain NoSuchElementException} if attempting to compare a value that
+ * could not be computed.
*
* The returned lazy map strongly references its underlying
* computing function used to compute values at least as long as there are
diff --git a/src/java.base/share/classes/java/util/Set.java b/src/java.base/share/classes/java/util/Set.java
index 0c66d4ef53c..3c76c88b12c 100644
--- a/src/java.base/share/classes/java/util/Set.java
+++ b/src/java.base/share/classes/java/util/Set.java
@@ -788,7 +788,11 @@ public interface Set extends Collection {
* {@linkplain Object#hashCode() hashCode()}, and
* {@linkplain Object#toString() toString()} methods may trigger initialization of
* one or more lazy elements. If initialization fails for at least one element,
- * the {@linkplain Object Object methods} may throw {@linkplain NoSuchElementException}.
+ * the {@linkplain Object#hashCode() hashCode()} and
+ * {@linkplain Object#toString() toString()} methods throw
+ * {@linkplain NoSuchElementException}, and the {@linkplain Object#equals(Object)}
+ * throw {@linkplain NoSuchElementException} if attempting to compare an element that
+ * could not be computed.
*
* The returned lazy set strongly references its underlying
* computing function used to compute membership status at least as long as there are
diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java
index 137cac45ed0..f39d92aeeb4 100644
--- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java
+++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java
@@ -418,19 +418,15 @@ public abstract class ForkJoinTask implements Future, Serializable {
for (;;) {
if ((s = status) < 0)
break;
- else if (interrupts < 0) {
- s = ABNORMAL; // interrupted and not done
- break;
- }
else if (Thread.interrupted()) {
- if (!ForkJoinPool.poolIsStopping(pool))
- interrupts = interruptible ? -1 : 1;
- else {
- interrupts = 1; // re-assert if cleared
+ if (ForkJoinPool.poolIsStopping(pool)) {
try {
cancel(true);
- } catch (Throwable ignore) {
- }
+ } catch (Throwable ignore) { }
+ }
+ if ((interrupts = interruptible ? -1 : 1) < 0) {
+ s = ABNORMAL;
+ break;
}
}
else if (deadline != 0L) {
diff --git a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java
index 72fb8036f08..88d08386e8c 100644
--- a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java
+++ b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -34,17 +34,55 @@ import java.io.EOFException;
import java.util.Objects;
/**
- * This class implements a stream filter for reading compressed data in
- * the GZIP file format.
+ * This class implements a stream filter for decompressing GZIP file format data.
+ *
+ *
+ * The GZIP file format is specified by RFC 1952. The format, as specified in section 2.2 of
+ * the RFC, consists of a series of "members" that appear one after another in the stream with
+ * no additional information before, between, or after them. Each member consists of a header,
+ * followed by data that is compressed using the {@code deflate} algorithm, and then a trailer.
+ *
+ * This class is capable of reading a stream consisting of a series of members.
+ *
+ * Reading from the stream may read and buffer bytes from the underlying stream.
+ * This includes bytes that follow a member's trailer. Whether or not any additional bytes
+ * have been read past a member's trailer, the read methods on this class yield decompressed
+ * data from at most one member; data from multiple members is not combined in
+ * a single read operation.
+ *
+ *
+ * {@code GZIPInputStream} is not safe for use by multiple concurrent threads. Any multithreaded
+ * concurrent use must be guarded by appropriate synchronization.
+ *
+ * @apiNote
+ * The {@link #close} method should be called to release resources used by this
+ * stream, either directly, or with the {@code try}-with-resources statement.
+ *
+ * @spec https://www.rfc-editor.org/info/rfc1952
+ * RFC 1952: GZIP file format specification version 4.3
+ *
+ * @see InflaterInputStream
*
- * @see InflaterInputStream
- * @author David Connelly
* @since 1.1
- *
*/
public class GZIPInputStream extends InflaterInputStream {
/**
- * CRC-32 for uncompressed data.
+ * GZIP header magic number.
+ */
+ public static final int GZIP_MAGIC = 0x8b1f;
+
+ /*
+ * File header flags.
+ */
+ private static final int FHCRC = 2; // Header CRC
+ private static final int FEXTRA = 4; // Extra field
+ private static final int FNAME = 8; // File name
+ private static final int FCOMMENT = 16; // File comment
+
+ private final byte[] tmpbuf = new byte[128];
+
+ /**
+ * CRC-32 for decompressed data.
*/
protected CRC32 crc = new CRC32();
@@ -66,13 +104,15 @@ public class GZIPInputStream extends InflaterInputStream {
/**
* Creates a new input stream with the specified buffer size.
+ *
* @param in the input stream
* @param size the input buffer size
*
* @throws ZipException if a GZIP format error has occurred or the
* compression method used is unsupported
* @throws NullPointerException if {@code in} is null
- * @throws IOException if an I/O error has occurred
+ * @throws IOException if an I/O error occurs when reading the member header
+ * from the underlying stream
* @throws IllegalArgumentException if {@code size <= 0}
*/
public GZIPInputStream(InputStream in, int size) throws IOException {
@@ -103,25 +143,27 @@ public class GZIPInputStream extends InflaterInputStream {
/**
* Creates a new input stream with a default buffer size.
+ *
* @param in the input stream
*
* @throws ZipException if a GZIP format error has occurred or the
* compression method used is unsupported
* @throws NullPointerException if {@code in} is null
- * @throws IOException if an I/O error has occurred
+ * @throws IOException if an I/O error occurs when reading the member header
+ * from the underlying stream
*/
public GZIPInputStream(InputStream in) throws IOException {
this(in, 512);
}
/**
- * Reads uncompressed data into an array of bytes, returning the number of inflated
+ * Reads decompressed data into an array of bytes, returning the number of decompressed
* bytes. If {@code len} is not zero, the method will block until some input can be
* decompressed; otherwise, no bytes are read and {@code 0} is returned.
*
* If this method returns a nonzero integer n then {@code buf[off]}
- * through {@code buf[off+}n{@code -1]} contain the uncompressed
- * data. The content of elements {@code buf[off+}n{@code ]} through
+ * through {@code buf[off+}n{@code -1]} contain the decompressed
+ * data. The content of elements {@code buf[off+}n{@code ]} through
* {@code buf[off+}len{@code -1]} is undefined, contrary to the
* specification of the {@link java.io.InputStream InputStream} superclass,
* so an implementation is free to modify these elements during the inflate
@@ -131,18 +173,20 @@ public class GZIPInputStream extends InflaterInputStream {
*
* @param buf the buffer into which the data is read
* @param off the start offset in the destination array {@code buf}
- * @param len the maximum number of bytes read
- * @return the actual number of bytes inflated, or -1 if the end of the
- * compressed input stream is reached
+ * @param len the maximum number of bytes to read into {@code buf}
+ * @return the actual number of bytes decompressed from a GZIP member, or -1 if the
+ * end-of-stream is reached
*
* @throws NullPointerException If {@code buf} is {@code null}.
* @throws IndexOutOfBoundsException If {@code off} is negative,
* {@code len} is negative, or {@code len} is greater than
* {@code buf.length - off}
* @throws ZipException if the compressed input data is corrupt.
- * @throws IOException if an I/O error has occurred.
+ * @throws IOException if the stream is closed or an I/O error has occurred.
*
+ * @see ##gzip_file_format GZIP file format
*/
+ @Override
public int read(byte[] buf, int off, int len) throws IOException {
ensureOpen();
if (eos) {
@@ -165,6 +209,7 @@ public class GZIPInputStream extends InflaterInputStream {
* with the stream.
* @throws IOException if an I/O error has occurred
*/
+ @Override
public void close() throws IOException {
if (!closed) {
super.close();
@@ -173,20 +218,6 @@ public class GZIPInputStream extends InflaterInputStream {
}
}
- /**
- * GZIP header magic number.
- */
- public static final int GZIP_MAGIC = 0x8b1f;
-
- /*
- * File header flags.
- */
- private static final int FTEXT = 1; // Extra text
- private static final int FHCRC = 2; // Header CRC
- private static final int FEXTRA = 4; // Extra field
- private static final int FNAME = 8; // File name
- private static final int FCOMMENT = 16; // File comment
-
/*
* Reads GZIP member header and returns the total byte number
* of this member header.
@@ -309,8 +340,6 @@ public class GZIPInputStream extends InflaterInputStream {
return b;
}
- private byte[] tmpbuf = new byte[128];
-
/*
* Skips bytes of input data blocking until all bytes are skipped.
* Does not assume that the input stream is capable of seeking.
diff --git a/src/java.base/share/classes/javax/crypto/CryptoException.java b/src/java.base/share/classes/javax/crypto/CryptoException.java
new file mode 100644
index 00000000000..367417abf9d
--- /dev/null
+++ b/src/java.base/share/classes/javax/crypto/CryptoException.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.crypto;
+
+import jdk.internal.javac.PreviewFeature;
+
+/**
+ * Thrown to indicate a cryptographic failure during processing.
+ *
+ *
This exception represents a general cryptographic error. It is typically
+ * used for unrecoverable failures related to
+ * {@link java.security.GeneralSecurityException} in contexts where checked
+ * exceptions are not desired.
+ *
+ *
This exception is not intended to represent internal provider errors,
+ * which should be reported using {@link java.security.ProviderException}.
+ *
+ * @since 27
+ */
+@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
+public final class CryptoException extends RuntimeException {
+
+ @java.io.Serial
+ private static final long serialVersionUID = -6824337376392797817L;
+
+ /**
+ * Constructs a new {@code CryptoException} with {@code null} as its detail
+ * message. The cause is not initialized and may subsequently be initialized
+ * by a call to {@link #initCause(Throwable)}.
+ */
+ public CryptoException() {
+ super();
+ }
+
+ /**
+ * Constructs a new {@code CryptoException} with the specified detail message.
+ * The cause is not initialized and may subsequently be initialized by a
+ * call to {@link #initCause(Throwable)}.
+ *
+ * @param message the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ */
+ public CryptoException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code CryptoException} with the specified detail
+ * message and cause.
+ *
+ *
Note that the detail message associated with {@code cause} is not
+ * automatically incorporated in this exception's detail message.
+ *
+ * @param message the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ * @param cause the cause. The cause is saved for later retrieval by the
+ * {@link #getCause()} method. A {@code null} value is permitted
+ * and indicates that the cause is nonexistent or unknown.
+ */
+ public CryptoException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new {@code CryptoException} with the specified cause and a detail
+ * message of {@code (cause == null ? null : cause.toString())}, which
+ * typically contains the class and detail message of {@code cause}.
+ *
+ * @param cause the cause. The cause is saved for later retrieval by the
+ * {@link #getCause()} method. A {@code null} value is permitted
+ * and indicates that the cause is nonexistent or unknown.
+ */
+ public CryptoException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
index dc3c4c14b27..632c81eab16 100644
--- a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
+++ b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -60,7 +60,7 @@ import java.util.Objects;
* @since 1.4
*/
-public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
+public non-sealed class EncryptedPrivateKeyInfo implements BinaryEncodable {
// The "encryptionAlgorithm" is stored in either the algid or
// the params field. Precisely, if this object is created by
@@ -221,7 +221,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Create an EncryptedPrivateKeyInfo object from the given components
+ * Create an EncryptedPrivateKeyInfo object from the given components.
*/
private EncryptedPrivateKeyInfo(byte[] encoded, byte[] eData,
AlgorithmId id, AlgorithmParameters p) {
@@ -265,8 +265,8 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
* Note: In order to successfully retrieve the enclosed
* PKCS8EncodedKeySpec object, {@code cipher} needs
* to be initialized to either Cipher.DECRYPT_MODE or
@@ -275,7 +275,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
*
* @param cipher the initialized {@code Cipher} object which will be
* used for decrypting the encrypted data.
- * @return the PKCS8EncodedKeySpec object.
+ * @return the PKCS8EncodedKeySpec object
* @exception NullPointerException if {@code cipher} is {@code null}.
* @exception InvalidKeySpecException if the given cipher is
* inappropriate for the encrypted data or the encrypted
@@ -283,7 +283,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
*/
public PKCS8EncodedKeySpec getKeySpec(Cipher cipher)
throws InvalidKeySpecException {
- byte[] encoded;
+ byte[] encoded = null;
try {
encoded = cipher.doFinal(encryptedData);
return pkcs8EncodingToSpec(encoded);
@@ -292,6 +292,8 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
IllegalStateException ex) {
throw new InvalidKeySpecException(
"Cannot retrieve the PKCS8EncodedKeySpec", ex);
+ } finally {
+ KeyUtil.clear(encoded);
}
}
@@ -338,7 +340,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
/**
* Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified
- * {@code DEREncodable}. A valid password-based encryption (PBE) algorithm
+ * {@code BinaryEncodable}. A valid password-based encryption (PBE) algorithm
* and password must be specified.
*
*
The format of the PBE algorithm string is described in the
@@ -346,7 +348,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
* Cipher Algorithms section of the Java Security Standard Algorithm Names
* Specification.
*
- * @param de the {@code DEREncodable} to encrypt. Supported types include
+ * @param be the {@code BinaryEncodable} to encrypt. Supported types include
* {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}.
* @param password the password used for PBE encryption. This array is cloned
* before use.
@@ -354,68 +356,73 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
* @param params the {@code AlgorithmParameterSpec} used for encryption. If
* {@code null}, the provider’s default parameters are applied.
* @param provider the {@code Provider} for {@code SecretKeyFactory} and
- * {@code Cipher} operations. If {@code null}, provider
- * defaults are used.
+ * {@code Cipher} operations. If {@code null}, the default
+ * provider list is used.
* @return an {@code EncryptedPrivateKeyInfo}
- * @throws NullPointerException if {@code de}, {@code password}, or
+ * @throws NullPointerException if {@code be}, {@code password}, or
* {@code algorithm} is {@code null}
- * @throws IllegalArgumentException if {@code de} is an unsupported
- * {@code DEREncodable}, if an error occurs while generating the
+ * @throws IllegalArgumentException if {@code be} is an unsupported
+ * {@code BinaryEncodable} or has no encoding
+ * @throws CryptoException if an error occurs while generating the
* PBE key, if {@code algorithm} or {@code params} are
* not supported by any provider, or if an error occurs during
- * encryption.
+ * encryption
*
- * @since 26
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public static EncryptedPrivateKeyInfo encrypt(DEREncodable de,
+ public static EncryptedPrivateKeyInfo encrypt(BinaryEncodable be,
char[] password, String algorithm, AlgorithmParameterSpec params,
Provider provider) {
- Objects.requireNonNull(de, "a key must be specified.");
- Objects.requireNonNull(password, "a password must be specified.");
- Objects.requireNonNull(algorithm, "an algorithm must be specified.");
+ Objects.requireNonNull(be, "a key must be specified");
+ Objects.requireNonNull(password, "a password must be specified");
+ Objects.requireNonNull(algorithm, "an algorithm must be specified");
char[] passwd = password.clone();
- byte[] encoding = getEncoding(de);
+ byte[] encoding = null;
+ SecretKey sk = null;
try {
- return encryptImpl(encoding, algorithm,
- generateSecretKey(passwd, algorithm, provider), params,
- provider, null);
+ encoding = getEncoding(be);
+ sk = generateSecretKey(passwd, algorithm, provider);
+ return encryptImpl(encoding, algorithm, sk, params, provider, null);
} finally {
+ KeyUtil.destroySecretKeys(sk);
KeyUtil.clear(passwd, encoding);
}
}
/**
* Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified
- * {@code DEREncodable}. A valid password must be specified. A default
+ * {@code BinaryEncodable}. A valid password must be specified. A default
* password-based encryption (PBE) algorithm and provider are used.
*
- * @param de the {@code DEREncodable} to encrypt. Supported types include
+ * @param be the {@code BinaryEncodable} to encrypt. Supported types include
* {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}.
* @param password the password used for PBE encryption. This array is cloned
* before use.
* @return an {@code EncryptedPrivateKeyInfo}
- * @throws NullPointerException if {@code de} or {@code password} is {@code null}
- * @throws IllegalArgumentException if {@code de} is an unsupported
- * {@code DEREncodable}, if an error occurs while generating the
- * PBE key, or if the default algorithm is misconfigured
+ * @throws NullPointerException if {@code be} or {@code password} is {@code null}
+ * @throws IllegalArgumentException if {@code be} is an unsupported
+ * {@code BinaryEncodable} or has no encoding
+ * @throws CryptoException if an error occurs while generating the
+ * PBE key, if the default algorithm is misconfigured, or if an
+ * error occurs during encryption
*
* @implNote The {@code jdk.epkcs8.defaultAlgorithm} security property
* defines the default encryption algorithm. The {@code AlgorithmParameterSpec}
* defaults are determined by the provider.
*
- * @since 26
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public static EncryptedPrivateKeyInfo encrypt(DEREncodable de,
+ public static EncryptedPrivateKeyInfo encrypt(BinaryEncodable be,
char[] password) {
- return encrypt(de, password, Pem.DEFAULT_ALGO, null,
+ return encrypt(be, password, Pem.DEFAULT_ALGO, null,
null);
}
/**
* Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified
- * {@code DEREncodable}. A valid encryption algorithm and {@code Key} must
+ * {@code BinaryEncodable}. A valid encryption algorithm and {@code Key} must
* be specified.
*
*
The format of the algorithm string is described in the
@@ -423,36 +430,37 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
* Cipher Algorithms section of the Java Security Standard Algorithm Names
* Specification.
*
- * @param de the {@code DEREncodable} to encrypt. Supported types include
+ * @param be the {@code BinaryEncodable} to encrypt. Supported types include
* {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}.
* @param encryptKey the key used to encrypt the encoding
* @param algorithm the encryption algorithm, such as a password-based
* encryption (PBE) algorithm
* @param params the {@code AlgorithmParameterSpec} used for encryption. If
* {@code null}, the provider’s default parameters are applied.
- * @param random the {@code SecureRandom} instance used during encryption.
- * If {@code null}, the default is used.
* @param provider the {@code Provider} for {@code Cipher} operations.
* If {@code null}, the default provider list is used.
+ * @param random the {@code SecureRandom} instance used during encryption.
+ * If {@code null}, the default is used.
* @return an {@code EncryptedPrivateKeyInfo}
- * @throws NullPointerException if {@code de}, {@code encryptKey}, or
+ * @throws NullPointerException if {@code be}, {@code encryptKey}, or
* {@code algorithm} is {@code null}
- * @throws IllegalArgumentException if {@code de} is an unsupported
- * {@code DEREncodable}, if {@code encryptKey} is invalid, if
+ * @throws IllegalArgumentException if {@code be} is an unsupported
+ * {@code BinaryEncodable} or has no encoding
+ * @throws CryptoException if {@code encryptKey} is invalid, if
* {@code algorithm} or {@code params} are not supported by any
* provider, or if an error occurs during encryption
*
- * @since 26
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public static EncryptedPrivateKeyInfo encrypt(DEREncodable de,
+ public static EncryptedPrivateKeyInfo encrypt(BinaryEncodable be,
Key encryptKey, String algorithm, AlgorithmParameterSpec params,
Provider provider, SecureRandom random) {
- Objects.requireNonNull(de, "a key must be specified.");
- Objects.requireNonNull(encryptKey, "an encryption key must be specified.");
- Objects.requireNonNull(algorithm, "an algorithm must be specified.");
- return encryptImpl(getEncoding(de), algorithm, encryptKey,
+ Objects.requireNonNull(be, "a key must be specified");
+ Objects.requireNonNull(encryptKey, "an encryption key must be specified");
+ Objects.requireNonNull(algorithm, "an algorithm must be specified");
+ return encryptImpl(getEncoding(be), algorithm, encryptKey,
params, provider, random);
}
@@ -489,7 +497,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException |
IllegalStateException | NoSuchPaddingException |
IllegalBlockSizeException | InvalidKeyException e) {
- throw new IllegalArgumentException(e);
+ throw new CryptoException(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
} finally {
@@ -517,39 +525,39 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
public PrivateKey getKey(char[] password)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(password, "a password must be specified.");
+ Objects.requireNonNull(password, "a password must be specified");
PBEKeySpec keySpec = new PBEKeySpec(password);
+ byte[] encoding = null;
try {
- return PKCS8Key.parseKey(Pem.decryptEncoding(this, keySpec), null);
+ encoding = Pem.decryptEncoding(this, keySpec);
+ return PKCS8Key.parseKey(encoding, null);
} finally {
keySpec.clearPassword();
+ KeyUtil.clear(encoding);
}
}
/**
* Extracts and returns the enclosed {@code PrivateKey} using the specified
- * decryption key and provider.
+ * decryption key.
*
- * @param decryptKey the decryption key. Must not be {@code null}.
- * @param provider the {@code Provider} for {@code Cipher} decryption
- * and {@code PrivateKey} generation. If {@code null}, the
- * default provider configuration is used.
+ * @param decryptKey the decryption key; must not be {@code null}
* @return the decrypted {@code PrivateKey}
* @throws NullPointerException if {@code decryptKey} is {@code null}
* @throws NoSuchAlgorithmException if the decryption algorithm is unsupported
* @throws InvalidKeyException if an error occurs during parsing,
* decryption, or key generation
*
- * @since 25
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public PrivateKey getKey(Key decryptKey, Provider provider)
+ public PrivateKey getKey(Key decryptKey)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(decryptKey,"a decryptKey must be specified.");
+ Objects.requireNonNull(decryptKey,"a decryptKey must be specified");
byte[] encoding = null;
try {
- encoding = decryptData(decryptKey, provider);
- return PKCS8Key.parseKey(encoding, provider);
+ encoding = decryptData(decryptKey, null);
+ return PKCS8Key.parseKey(encoding, null);
} finally {
KeyUtil.clear(encoding);
}
@@ -573,19 +581,22 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
public KeyPair getKeyPair(char[] password)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(password, "a password must be specified.");
+ Objects.requireNonNull(password, "a password must be specified");
PBEKeySpec keySpec = new PBEKeySpec(password);
- DEREncodable d;
+ BinaryEncodable d;
+ byte[] encoding = null;
try {
- d = Pem.toDEREncodable(Pem.decryptEncoding(this, keySpec), true, null);
+ encoding = Pem.decryptEncoding(this, keySpec);
+ d = Pem.toPKCS8Encodable(encoding, null);
} finally {
keySpec.clearPassword();
+ KeyUtil.clear(encoding);
}
return switch (d) {
case KeyPair kp -> kp;
case PrivateKey ignored -> throw new InvalidKeyException(
- "This encoding does not contain a public key.");
+ "This encoding does not contain a public key");
default -> throw new InvalidKeyException(
"Invalid class returned " + d.getClass().getName());
};
@@ -593,49 +604,52 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
/**
* Extracts and returns the enclosed {@code KeyPair} using the specified
- * decryption key and provider. If the encoded data does not contain both a
+ * decryption key. If the encoded data does not contain both a
* public and private key, an {@code InvalidKeyException} is thrown.
*
- * @param decryptKey the decryption key. Must not be {@code null}.
- * @param provider the {@code Provider} for {@code Cipher} decryption
- * and key generation. If {@code null}, the default provider
- * configuration is used.
+ * @param decryptKey the decryption key; must not be {@code null}
* @return a decrypted {@code KeyPair}
* @throws NullPointerException if {@code decryptKey} is {@code null}
* @throws NoSuchAlgorithmException if the decryption algorithm is unsupported
* @throws InvalidKeyException if the encoded data lacks a public key, or if
* an error occurs during parsing, decryption, or key generation
*
- * @since 26
+ * @since 27
*/
@PreviewFeature(feature = PreviewFeature.Feature.PEM_API)
- public KeyPair getKeyPair(Key decryptKey, Provider provider)
+ public KeyPair getKeyPair(Key decryptKey)
throws NoSuchAlgorithmException, InvalidKeyException {
- Objects.requireNonNull(decryptKey,"a decryptKey must be specified.");
+ Objects.requireNonNull(decryptKey,"a decryptKey must be specified");
- DEREncodable d = Pem.toDEREncodable(
- decryptData(decryptKey, provider),true, provider);
+ BinaryEncodable d;
+ byte[] encoding = null;
+ try {
+ encoding = decryptData(decryptKey, null);
+ d = Pem.toPKCS8Encodable(encoding, null);
+ } finally {
+ KeyUtil.clear(encoding);
+ }
return switch (d) {
case KeyPair kp -> kp;
case PrivateKey ignored -> throw new InvalidKeyException(
- "This encoding does not contain a public key.");
+ "This encoding does not contain a public key");
default -> throw new InvalidKeyException(
"Invalid class returned " + d.getClass().getName());
};
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
* @param decryptKey key used for decrypting the encrypted data.
- * @return the PKCS8EncodedKeySpec object.
+ * @return the PKCS8EncodedKeySpec object with a specified algorithm
* @exception NullPointerException if {@code decryptKey}
* is {@code null}.
* @exception NoSuchAlgorithmException if cannot find appropriate
* cipher to decrypt the encrypted data.
* @exception InvalidKeyException if {@code decryptKey}
* cannot be used to decrypt the encrypted data or the decryption
- * result is not a valid PKCS8KeySpec.
+ * result is not a valid PKCS8EncodedKeySpec.
*
* @since 1.5
*/
@@ -648,12 +662,12 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
* @param decryptKey key used for decrypting the encrypted data.
* @param providerName the name of provider whose cipher
* implementation will be used.
- * @return the PKCS8EncodedKeySpec object
+ * @return the PKCS8EncodedKeySpec object with a specified algorithm
* @exception NullPointerException if {@code decryptKey}
* or {@code providerName} is {@code null}.
* @exception NoSuchProviderException if no provider
@@ -662,7 +676,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
* cipher to decrypt the encrypted data.
* @exception InvalidKeyException if {@code decryptKey}
* cannot be used to decrypt the encrypted data or the decryption
- * result is not a valid PKCS8KeySpec.
+ * result is not a valid PKCS8EncodedKeySpec.
*
* @since 1.5
*/
@@ -670,7 +684,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
String providerName) throws NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeyException {
Objects.requireNonNull(decryptKey, "decryptKey is null");
- Objects.requireNonNull(providerName, "provider is null");
+ Objects.requireNonNull(providerName, "providerName is null");
Provider provider = Security.getProvider(providerName);
if (provider == null) {
throw new NoSuchProviderException("provider " +
@@ -680,19 +694,18 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
/**
- * Extract the enclosed PKCS8EncodedKeySpec object from the
- * encrypted data and return it.
+ * Extracts the enclosed PKCS8EncodedKeySpec object from the
+ * encrypted data and returns it.
* @param decryptKey key used for decrypting the encrypted data.
- * @param provider the name of provider whose cipher implementation
- * will be used.
- * @return the PKCS8EncodedKeySpec object.
+ * @param provider the provider whose cipher implementation will be used.
+ * @return the PKCS8EncodedKeySpec object with a specified algorithm
* @exception NullPointerException if {@code decryptKey}
* or {@code provider} is {@code null}.
* @exception NoSuchAlgorithmException if cannot find appropriate
* cipher to decrypt the encrypted data in {@code provider}.
* @exception InvalidKeyException if {@code decryptKey}
* cannot be used to decrypt the encrypted data or the decryption
- * result is not a valid PKCS8KeySpec.
+ * result is not a valid PKCS8EncodedKeySpec.
*
* @since 1.5
*/
@@ -745,22 +758,26 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
KeyUtil.getAlgorithm(encodedKey));
}
- // Return the PKCS#8 encoding from a DEREncodable
- private static byte[] getEncoding(DEREncodable d) {
- return switch (d) {
- case PrivateKey p -> p.getEncoded();
- case PKCS8EncodedKeySpec p8 -> p8.getEncoded();
- case KeyPair kp -> {
- try {
- yield PKCS8Key.getEncoded(kp.getPublic().getEncoded(),
- kp.getPrivate().getEncoded());
- } catch (IOException e) {
- throw new IllegalArgumentException(e);
+ // Return the PKCS#8 encoding from a BinaryEncodable
+ private static byte[] getEncoding(BinaryEncodable d) {
+ try {
+ return switch (d) {
+ case PrivateKey p -> p.getEncoded();
+ case PKCS8EncodedKeySpec p8 -> p8.getEncoded();
+ case KeyPair kp -> {
+ try {
+ yield PKCS8Key.getEncoded(kp.getPublic().getEncoded(),
+ kp.getPrivate().getEncoded());
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
}
- }
- default -> throw new IllegalArgumentException(
- d.getClass().getName() + " not supported by this method");
- };
+ default -> throw new IllegalArgumentException(
+ d.getClass().getName() + " not supported by this method");
+ };
+ } catch (NullPointerException e) {
+ throw new IllegalArgumentException(e);
+ }
}
// Generate a SecretKey from the password.
@@ -777,7 +794,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable {
}
return factory.generateSecret(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
- throw new IllegalArgumentException(e);
+ throw new CryptoException(e);
} finally {
keySpec.clearPassword();
}
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 82c55d6f017..064e4e1fd92 100644
--- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
+++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
@@ -68,8 +68,8 @@ public @interface PreviewFeature {
STRUCTURED_CONCURRENCY,
@JEP(number = 531, title = "Lazy Constants", status = "Third Preview")
LAZY_CONSTANTS,
- @JEP(number=524, title="PEM Encodings of Cryptographic Objects",
- status="Second Preview")
+ @JEP(number=538, title="PEM Encodings of Cryptographic Objects",
+ status="Third Preview")
PEM_API,
LANGUAGE_MODEL,
/**
diff --git a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java
index e5a1dd62c11..3381810917d 100644
--- a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java
+++ b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java
@@ -27,6 +27,7 @@ package jdk.internal.lang;
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
+import jdk.internal.vm.annotation.DontInline;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
@@ -84,35 +85,35 @@ public final class LazyConstantImpl implements LazyConstant {
return (t != null) ? t : getSlowPath();
}
- @SuppressWarnings("unchecked")
+ @DontInline
private T getSlowPath() {
preventReentry();
synchronized (this) {
T t = getAcquire();
if (t == null) {
- switch (computingFunctionOrExceptionType) {
- case Supplier> computingFunction -> {
- try {
- @SuppressWarnings("unchecked")
- final T newT = (T) computingFunction.get();
- t = newT;
- Objects.requireNonNull(t);
- setRelease(t);
- // Allow the underlying supplier to be collected after
- // a successful initialization
- computingFunctionOrExceptionType = null;
- } catch (Throwable ex) {
- // Release the original computing function and replace it with
- // an exception marker
- final String exceptionType = ex.getClass().getName().intern();
- computingFunctionOrExceptionType = exceptionType;
- throw unableToAccessConstant(exceptionType, ex);
- }
+ final Object cf = computingFunctionOrExceptionType;
+ // Don't use switch pattern matching here in order to improve startup time.
+ if (cf instanceof Supplier> computingFunction) {
+ try {
+ @SuppressWarnings("unchecked")
+ final T newT = (T) computingFunction.get();
+ t = newT;
+ Objects.requireNonNull(t);
+ setRelease(t);
+ // Allow the underlying supplier to be collected after
+ // a successful initialization
+ computingFunctionOrExceptionType = null;
+ } catch (Throwable ex) {
+ // Release the original computing function and replace it with
+ // an exception marker
+ final String exceptionType = ex.getClass().getName().intern();
+ computingFunctionOrExceptionType = exceptionType;
+ throw unableToAccessConstant(exceptionType, ex);
}
- case String exceptionType ->
- throw unableToAccessConstant(exceptionType, null);
- default ->
- throw new InternalError("Cannot reach here");
+ } else if (cf instanceof String exceptionType) {
+ throw unableToAccessConstant(exceptionType, null);
+ } else {
+ throw new InternalError("Cannot reach here");
}
}
return t;
diff --git a/src/java.base/share/classes/jdk/internal/util/Architecture.java b/src/java.base/share/classes/jdk/internal/util/Architecture.java
index 4f193e75597..31f328c2ead 100644
--- a/src/java.base/share/classes/jdk/internal/util/Architecture.java
+++ b/src/java.base/share/classes/jdk/internal/util/Architecture.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -37,15 +37,15 @@ import java.util.Locale;
* architecture values.
*/
public enum Architecture {
- /*
- * An unknown architecture not specifically named.
- * The addrSize and ByteOrder values are those of the current architecture.
- */
AARCH64(64, ByteOrder.LITTLE_ENDIAN),
ARM(32, ByteOrder.LITTLE_ENDIAN),
LOONGARCH64(64, ByteOrder.LITTLE_ENDIAN),
MIPSEL(32, ByteOrder.LITTLE_ENDIAN),
MIPS64EL(64, ByteOrder.LITTLE_ENDIAN),
+ /*
+ * An unknown architecture not specifically named.
+ * The addrSize and ByteOrder values are those of the current architecture.
+ */
OTHER(is64bit() ? 64 : 32, ByteOrder.nativeOrder()),
PPC(32, ByteOrder.BIG_ENDIAN),
PPC64(64, ByteOrder.BIG_ENDIAN),
diff --git a/src/java.base/share/classes/sun/security/provider/X509Factory.java b/src/java.base/share/classes/sun/security/provider/X509Factory.java
index 154d1428414..9a4f3717065 100644
--- a/src/java.base/share/classes/sun/security/provider/X509Factory.java
+++ b/src/java.base/share/classes/sun/security/provider/X509Factory.java
@@ -581,7 +581,7 @@ public class X509Factory extends CertificateFactorySpi {
} catch (EOFException e) {
return null;
}
- return Base64.getDecoder().decode(rec.content());
+ return Base64.getMimeDecoder().decode(rec.content());
} catch (IllegalArgumentException e) {
throw new IOException(e);
}
diff --git a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java
index 9d1d6dabaec..a0fc6c6e207 100644
--- a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java
+++ b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,7 @@
package sun.security.ssl;
import sun.security.ssl.Authenticator.MAC;
+import sun.security.util.Debug;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -371,15 +372,15 @@ enum SSLCipher {
ProtocolVersion[]>[] writeCipherGenerators;
// Map of Ciphers listed in jdk.tls.keyLimits
- private static final HashMap cipherLimits = new HashMap<>();
+ static final HashMap cipherLimits = new HashMap<>();
// Keywords found on the jdk.tls.keyLimits security property.
static final String[] tag = {"KEYUPDATE"};
+ static final long COUNTDOWNWARN = 20000; // Print debug warning under limit
static {
final long max = 4611686018427387904L; // 2^62
String prop = Security.getProperty("jdk.tls.keyLimits");
-
if (prop != null) {
String[] propvalue = prop.split(",");
@@ -617,12 +618,21 @@ enum SSLCipher {
/**
* Check if processed bytes have reached the key usage limit.
- * If key usage limit is not be monitored, return false.
+ * If key usage limits are not be monitored, return false.
*/
public boolean atKeyLimit() {
+ if (keyLimitCountdown < COUNTDOWNWARN && SSLLogger.isOn()) {
+ SSLLogger.fine("keyLimitCountdown: " + keyLimitCountdown);
+ }
if (keyLimitCountdown >= 0) {
return false;
}
+ if (keyLimitEnabled == false) {
+ if (SSLLogger.isOn()) {
+ SSLLogger.fine("KeyUpdate already sent, skipping");
+ }
+ return false;
+ }
// Turn off limit checking as KeyUpdate will be occurring
keyLimitEnabled = false;
diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java
index af0b8909d30..45882a79fd7 100644
--- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -40,7 +40,7 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.zip.Adler32;
+import java.util.zip.CRC32C;
import javax.crypto.KDF;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -610,9 +610,9 @@ final class SSLSessionImpl extends ExtendedSSLSession {
}
private static int getChecksum(byte[] input) {
- Adler32 adler32 = new Adler32();
- adler32.update(input);
- return (int) adler32.getValue();
+ CRC32C crc32c = new CRC32C();
+ crc32c.update(input);
+ return (int) crc32c.getValue();
}
void setMasterSecret(SecretKey secret) {
diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java
index e9dabdc5b06..5a14deb70a4 100644
--- a/src/java.base/share/classes/sun/security/util/KeyUtil.java
+++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java
@@ -31,6 +31,7 @@ import java.security.*;
import java.security.interfaces.*;
import java.security.spec.*;
import java.util.Arrays;
+import java.util.function.Function;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHKey;
import javax.crypto.interfaces.DHPublicKey;
@@ -568,5 +569,25 @@ public final class KeyUtil {
}
}
}
+
+ /**
+ * Executes {@code op} with {@code encoding} and then zeroes {@code encoding}
+ * in a {@code finally} block before returning or propagating an exception.
+ *
+ * {@code encoding} is temporary sensitive data and is always wiped.
+ *
+ * Usage constraint: {@code op} must not return {@code encoding} itself, or
+ * any value backed by the same array. Otherwise, the returned data will already
+ * be zeroed when this method returns.
+ */
+ public static T clear(byte[] encoding, Function op) {
+ try {
+ return op.apply(encoding);
+ } finally {
+ if (encoding != null) {
+ Arrays.fill(encoding, (byte)0);
+ }
+ }
+ }
}
diff --git a/src/java.base/share/classes/sun/security/util/Pem.java b/src/java.base/share/classes/sun/security/util/Pem.java
index dac3eeec8b8..ae29951c4ef 100644
--- a/src/java.base/share/classes/sun/security/util/Pem.java
+++ b/src/java.base/share/classes/sun/security/util/Pem.java
@@ -29,6 +29,7 @@ import sun.security.pkcs.PKCS8Key;
import sun.security.x509.AlgorithmId;
import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.*;
@@ -47,7 +48,11 @@ import java.util.regex.Pattern;
* A utility class for PEM format encoding.
*/
public class Pem {
- private static final byte[] CRLF = new byte[] {'\r', '\n'};
+ private static final byte[] CRLF = new byte[]{'\r', '\n'};
+ private static final byte[] DASH;
+ private static final byte[] BEGIN_B;
+ private static final byte[] BEGIN_PREFIX;
+ private static final byte[] END_PREFIX;
// Default algorithm from jdk.epkcs8.defaultAlgorithm in java.security
public static final String DEFAULT_ALGO;
@@ -62,10 +67,10 @@ public class Pem {
private static final Pattern LINE_WRAP_64_PATTERN;
// Lazy initialized PBES2 OID value
- private static ObjectIdentifier PBES2OID;
+ private static volatile ObjectIdentifier PBES2OID;
// Lazy initialized singleton encoder.
- private static Base64.Encoder b64Encoder;
+ private static volatile Base64.Encoder b64Encoder;
static {
String algo = Security.getProperty("jdk.epkcs8.defaultAlgorithm");
@@ -75,6 +80,10 @@ public class Pem {
Pattern.CASE_INSENSITIVE);
STRIP_WHITESPACE_PATTERN = Pattern.compile("\\s+");
LINE_WRAP_64_PATTERN = Pattern.compile("(.{64})");
+ DASH = "-----".getBytes(StandardCharsets.ISO_8859_1);
+ BEGIN_B = "-----B".getBytes(StandardCharsets.ISO_8859_1);
+ BEGIN_PREFIX = "-----BEGIN ".getBytes(StandardCharsets.ISO_8859_1);
+ END_PREFIX = "-----END ".getBytes(StandardCharsets.ISO_8859_1);
}
public static final String CERTIFICATE = "CERTIFICATE";
@@ -162,13 +171,10 @@ public class Pem {
* @param shortHeader if true, the hyphen length is 4 because the first
* hyphen is assumed to have been read. This is needed
* for the CertificateFactory X509 implementation.
- * @return a new PEMRecord
+ * @return a PEM instance
* @throws IOException on IO errors or PEM syntax errors that leave
* the read position not at the end of a PEM block
* @throws EOFException when at the unexpected end of the stream
- * @throws IllegalArgumentException when a PEM syntax error occurs,
- * but the read position in the stream is at the end of the block, so
- * future reads can be successful.
*/
public static PEM readPEM(InputStream is, boolean shortHeader)
throws IOException {
@@ -176,257 +182,268 @@ public class Pem {
int hyphen = (shortHeader ? 1 : 0);
int eol = 0;
- ByteArrayOutputStream os = new ByteArrayOutputStream(6);
+ var os = new ClearableBufferStream(6); // preData
+ var readbuf = new ByteArrayOutputStream(64); // header/footer
+ var pem = new ClearableBufferStream(1024); // PEM
+ String headerType, footerType;
+ byte[] encoding = null;
- // Find 5 hyphens followed by a 'B' to start processing the header.
- boolean headerStarted = false;
- do {
- int d = is.read();
- switch (d) {
- case '-' -> hyphen++;
- case -1 -> {
- if (os.size() == 0) {
- throw new EOFException("No data available");
+ try {
+ // Find 5 hyphens followed by a 'B' to start processing the header.
+ boolean headerStarted = false;
+ do {
+ int d = is.read();
+ switch (d) {
+ case '-' -> hyphen++;
+ case -1 -> {
+ if (os.size() == 0) {
+ throw new EOFException("No data available");
+ }
+ throw new EOFException("No PEM data found");
}
- throw new EOFException("No PEM data found");
- }
- case 'B' -> {
- if (hyphen == 5) {
- headerStarted = true;
- } else {
- hyphen = 0;
+ case 'B' -> {
+ if (hyphen == 5) {
+ headerStarted = true;
+ } else {
+ hyphen = 0;
+ }
}
+ default -> hyphen = 0;
}
- default -> hyphen = 0;
- }
- os.write(d);
- } while (!headerStarted);
+ os.write(d);
+ } while (!headerStarted);
- StringBuilder sb = new StringBuilder(64);
- sb.append("-----B");
- hyphen = 0;
- int c;
+ readbuf.writeBytes(BEGIN_B);
+ hyphen = 0;
+ int c;
- // Get header definition until first hyphen
- do {
- switch (c = is.read()) {
- case '-' -> hyphen++;
- case -1 -> throw new EOFException("Input ended prematurely");
- case '\n', '\r' -> throw new IOException("Incomplete header");
- default -> sb.append((char) c);
- }
- } while (hyphen == 0);
+ // Get header definition until first hyphen
+ do {
+ switch (c = is.read()) {
+ case '-' -> hyphen++;
+ case -1 -> throw new EOFException("Input ended prematurely");
+ case '\n', '\r' -> throw new IOException("Incomplete header");
+ default -> readbuf.write(c);
+ }
+ } while (hyphen == 0);
- // Verify header ending with 5 hyphens.
- do {
- switch (is.read()) {
- case '-' -> hyphen++;
- default ->
+ // Verify header ending with 5 hyphens.
+ do {
+ if (is.read() == '-') {
+ hyphen++;
+ } else {
throw new IOException("Incomplete header");
+ }
+ } while (hyphen < 5);
+
+ readbuf.writeBytes(DASH);
+ byte[] header = readbuf.toByteArray();
+ if (header.length < 16 ||
+ !matchesAt(header, 0, BEGIN_PREFIX) ||
+ !matchesAt(header, header.length - DASH.length, DASH)) {
+ throw new IOException("Illegal header: " +
+ new String(header, StandardCharsets.ISO_8859_1));
}
- } while (hyphen < 5);
- sb.append("-----");
- String header = sb.toString();
- if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
- !header.endsWith("-----")) {
- throw new IOException("Illegal header: " + header);
- }
+ hyphen = 0;
+ readbuf.reset();
- hyphen = 0;
- sb = new StringBuilder(1024);
+ // Determine the line break using the char after the last hyphen
+ while (eol == 0) {
+ switch (is.read()) {
+ case '\s', '\t' -> {} // skip whitespace or tab
+ case '\r' -> {
+ c = is.read();
+ if (c == '\n') {
+ eol = '\n';
+ } else {
+ eol = '\r';
+ pem.write(c);
+ }
+ }
+ case '\n' -> eol = '\n';
+ default -> throw new IOException("No EOL character found");
+ }
+ }
- // Determine the line break using the char after the last hyphen
- while (eol == 0) {
- switch (is.read()) {
- case '\s', '\t' -> {} // skip whitespace or tab
- case '\r' -> {
- c = is.read();
- if (c == '\n') {
- eol = '\n';
- } else {
- eol = '\r';
- sb.append((char) c);
+ // Read data until we find the first footer hyphen.
+ // CR & LF are allowed to support legacy PEM formats (ie: encrypted PKCS1)
+ do {
+ switch (c = is.read()) {
+ case -1 -> throw new EOFException("Incomplete header");
+ case '-' -> hyphen++;
+ default -> {
+ // If reading a legacy format, allow for one dash
+ if (hyphen == 1) {
+ hyphen = 0;
+ pem.write('-');
+ }
+ pem.write(c);
}
}
- case '\n' -> eol = '\n';
- default -> throw new IOException("No EOL character found");
+ } while (hyphen < 2);
+
+ // Verify footer starts with 5 hyphens.
+ do {
+ switch (is.read()) {
+ case '-' -> hyphen++;
+ case -1 ->
+ throw new EOFException("Input ended prematurely");
+ default -> throw new IOException("Incomplete footer");
+ }
+ } while (hyphen < 5);
+
+ hyphen = 0;
+ readbuf.reset();
+ readbuf.writeBytes(DASH);
+
+ // Look for Complete header by looking for the end of the hyphens
+ do {
+ switch (c = is.read()) {
+ case '-' -> hyphen++;
+ case -1 ->
+ throw new EOFException("Input ended prematurely");
+ default -> readbuf.write(c);
+ }
+ } while (hyphen == 0);
+
+ // Verify ending with 5 hyphens.
+ do {
+ switch (is.read()) {
+ case '-' -> hyphen++;
+ case -1 ->
+ throw new EOFException("Input ended prematurely");
+ default -> throw new IOException("Incomplete footer");
+ }
+ } while (hyphen < 5);
+
+ while ((c = is.read()) != eol && c != -1) {
+ // skip when eol is '\n', the line separator is likely "\r\n".
+ if (c == '\r' || c == '\s' || c == '\t') {
+ continue;
+ }
+ throw new IOException("Invalid PEM format: " +
+ "No EOL char found in footer: 0x" +
+ HexFormat.of().toHexDigits((byte) c));
}
+
+ readbuf.writeBytes(DASH);
+ byte[] footer = readbuf.toByteArray();
+ if (footer.length < 14 ||
+ !matchesAt(footer, 0, END_PREFIX) ||
+ !matchesAt(footer, footer.length - DASH.length, DASH)) {
+
+ // Not an IOE because the read pointer is correctly at the end.
+ throw new IOException("Illegal footer: " +
+ new String(footer, StandardCharsets.ISO_8859_1));
+ }
+
+ // Verify the object type in the header and the footer are the same.
+ headerType = new String(header, 11, header.length - 16,
+ StandardCharsets.ISO_8859_1);
+ footerType = new String(footer, 9, footer.length - 14,
+ StandardCharsets.ISO_8859_1);
+ if (!headerType.equals(footerType)) {
+ throw new IOException("Header and footer do not " +
+ "match: " + headerType + " " + footerType);
+ }
+
+ // If there was data before finding the 5 dashes of the PEM header,
+ // backup 5 characters and save that data.
+ byte[] preData = null;
+ if (os.size() > 6) {
+ preData = Arrays.copyOf(os.getBuffer(), os.size() - 6);
+ }
+
+ encoding = pem.toByteArray();
+ return (preData == null) ?
+ new PEM(typeConverter(headerType), encoding) :
+ new PEM(typeConverter(headerType), encoding, preData);
+ } finally {
+ KeyUtil.clear(encoding);
+ os.close();
+ pem.clear();
+ pem.close();
+ readbuf.close();
}
- // Read data until we find the first footer hyphen.
- do {
- switch (c = is.read()) {
- case -1 ->
- throw new EOFException("Incomplete header");
- case '-' -> hyphen++;
- case '\s', '\t', '\r', '\n' -> {} // skip whitespace and tab
- default -> sb.append((char) c);
- }
- } while (hyphen == 0);
-
- String data = sb.toString();
-
- // Verify footer starts with 5 hyphens.
- do {
- switch (is.read()) {
- case '-' -> hyphen++;
- case -1 -> throw new EOFException("Input ended prematurely");
- default -> throw new IOException("Incomplete footer");
- }
- } while (hyphen < 5);
-
- hyphen = 0;
- sb = new StringBuilder(64);
- sb.append("-----");
-
- // Look for Complete header by looking for the end of the hyphens
- do {
- switch (c = is.read()) {
- case '-' -> hyphen++;
- case -1 -> throw new EOFException("Input ended prematurely");
- default -> sb.append((char) c);
- }
- } while (hyphen == 0);
-
- // Verify ending with 5 hyphens.
- do {
- switch (is.read()) {
- case '-' -> hyphen++;
- case -1 -> throw new EOFException("Input ended prematurely");
- default -> throw new IOException("Incomplete footer");
- }
- } while (hyphen < 5);
-
- while ((c = is.read()) != eol && c != -1 && c != '\s' && c != '\t') {
- // skip when eol is '\n', the line separator is likely "\r\n".
- if (c == '\r') {
- continue;
- }
- throw new IOException("Invalid PEM format: " +
- "No EOL char found in footer: 0x" +
- HexFormat.of().toHexDigits((byte) c));
- }
-
- sb.append("-----");
- String footer = sb.toString();
- if (footer.length() < 14 || !footer.startsWith("-----END ") ||
- !footer.endsWith("-----")) {
- // Not an IOE because the read pointer is correctly at the end.
- throw new IOException("Illegal footer: " + footer);
- }
-
- // Verify the object type in the header and the footer are the same.
- String headerType = header.substring(11, header.length() - 5);
- String footerType = footer.substring(9, footer.length() - 5);
- if (!headerType.equals(footerType)) {
- throw new IOException("Header and footer do not " +
- "match: " + headerType + " " + footerType);
- }
-
- // If there was data before finding the 5 dashes of the PEM header,
- // backup 5 characters and save that data.
- byte[] preData = null;
- if (os.size() > 6) {
- preData = Arrays.copyOf(os.toByteArray(), os.size() - 6);
- }
-
- return new PEM(typeConverter(headerType), data, preData);
}
public static PEM readPEM(InputStream is) throws IOException {
return readPEM(is, false);
}
- private static String pemEncoded(String type, String base64) {
- return
- "-----BEGIN " + type + "-----\r\n" +
- base64 + (!base64.endsWith("\n") ? "\r\n" : "") +
- "-----END " + type + "-----\r\n";
+ /**
+ * Return a PEM encoding with the given type and base64 byte array.
+ */
+ public static byte[] pemEncoded(String type, byte[] base64) {
+ byte[] header = ("-----BEGIN " + type + "-----\r\n")
+ .getBytes(StandardCharsets.ISO_8859_1);
+ byte[] footer = ("-----END " + type + "-----\r\n")
+ .getBytes(StandardCharsets.ISO_8859_1);
+
+ int crlfLen = (base64.length == 0 ||
+ base64[base64.length - 1] != '\n') ? 2 : 0;
+ byte[] result = new byte[header.length + base64.length +
+ crlfLen + footer.length];
+ System.arraycopy(header, 0, result, 0, header.length);
+ System.arraycopy(base64, 0, result, header.length, base64.length);
+ if (crlfLen == 2) {
+ result[header.length + base64.length] = '\r';
+ result[header.length + base64.length + 1] = '\n';
+ }
+ System.arraycopy(footer, 0, result,
+ header.length + base64.length + crlfLen, footer.length);
+ return result;
}
- /**
- * Construct a String-based encoding based off the type. leadingData
- * is not used with this method.
- * @return PEM in a string
- */
- public static String pemEncoded(String type, byte[] der) {
+ public static byte[] pemEncodedFromDER(String type, byte[] der) {
if (b64Encoder == null) {
b64Encoder = Base64.getMimeEncoder(64, CRLF);
}
- return pemEncoded(type, b64Encoder.encodeToString(der));
+ return KeyUtil.clear(b64Encoder.encode(der), e -> pemEncoded(type, e));
}
/**
- * Construct a String-based encoding based off the type. leadingData
- * is not used with this method.
- * @return PEM in a string
+ * Decrypt the EncryptedPrivateKeyInfo with the given keySpec and
+ * return the PKCS#8 byte array
*/
- public static String pemEncoded(PEM pem) {
- String p = LINE_WRAP_64_PATTERN.matcher(pem.content()).replaceAll("$1\r\n");
- return pemEncoded(pem.type(), p);
- }
-
- /*
- * Get PKCS8 encoding from an encrypted private key encoding.
- */
- public static byte[] decryptEncoding(byte[] encoded, char[] password)
- throws GeneralSecurityException {
- EncryptedPrivateKeyInfo ekpi;
-
- Objects.requireNonNull(password, "password cannot be null");
- PBEKeySpec keySpec = new PBEKeySpec(password);
- try {
- ekpi = new EncryptedPrivateKeyInfo(encoded);
- return decryptEncoding(ekpi, keySpec);
- } catch (IOException e) {
- throw new IllegalArgumentException(e);
- } finally {
- keySpec.clearPassword();
- }
- }
-
- public static byte[] decryptEncoding(EncryptedPrivateKeyInfo ekpi, PBEKeySpec keySpec)
- throws NoSuchAlgorithmException, InvalidKeyException {
+ public static byte[] decryptEncoding(EncryptedPrivateKeyInfo ekpi,
+ PBEKeySpec keySpec) throws NoSuchAlgorithmException,
+ InvalidKeyException {
PKCS8EncodedKeySpec p8KeySpec = null;
+ SecretKeyFactory skf = SecretKeyFactory.getInstance(ekpi.getAlgName());
+ SecretKey sk = null;
try {
- SecretKeyFactory skf = SecretKeyFactory.getInstance(ekpi.getAlgName());
- p8KeySpec = ekpi.getKeySpec(skf.generateSecret(keySpec));
+ sk = skf.generateSecret(keySpec);
+ p8KeySpec = ekpi.getKeySpec(sk);
return p8KeySpec.getEncoded();
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
} finally {
+ KeyUtil.destroySecretKeys(sk);
KeyUtil.clear(p8KeySpec);
}
}
-
/**
* With a given PKCS8 encoding, construct a PrivateKey or KeyPair. A
* KeyPair is returned if requested and the encoding has a public key;
* otherwise, a PrivateKey is returned.
*
* @param encoded PKCS8 encoding
- * @param pair set to true for returning a KeyPair, if possible. Otherwise,
- * return a PrivateKey
* @param provider KeyFactory provider
*/
- public static DEREncodable toDEREncodable(byte[] encoded, boolean pair,
+ public static BinaryEncodable toPKCS8Encodable(byte[] encoded,
Provider provider) throws InvalidKeyException {
PrivateKey privKey;
PublicKey pubKey = null;
+ KeyFactory kf;
PKCS8EncodedKeySpec p8KeySpec;
PKCS8Key p8key = new PKCS8Key(encoded);
- KeyFactory kf;
-
- try {
- p8KeySpec = new PKCS8EncodedKeySpec(encoded);
- } catch (NullPointerException e) {
- p8key.clear();
- throw new InvalidKeyException("No encoding found", e);
- }
+ p8KeySpec = new PKCS8EncodedKeySpec(encoded);
try {
if (provider == null) {
@@ -442,12 +459,6 @@ public class Pem {
try {
privKey = kf.generatePrivate(p8KeySpec);
-
- // Only want the PrivateKey? then return it.
- if (!pair) {
- return privKey;
- }
-
if (p8key.hasPublicKey()) {
// PKCS8Key.decode() has extracted the public key already
pubKey = kf.generatePublic(
@@ -467,10 +478,43 @@ public class Pem {
} finally {
KeyUtil.clear(p8KeySpec, p8key);
}
- if (pair && pubKey != null) {
+ if (pubKey != null) {
return new KeyPair(pubKey, privKey);
}
return privKey;
}
+ private static boolean matchesAt(byte[] source, int offset, byte[] match) {
+ for (int i = 0; i < match.length; i++) {
+ if (source[offset + i] != match[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Clearable ByteArrayOutputStream for temporary data. Access to the
+ * internal buffer is allowed to limit data copying. Handle with care.
+ */
+ private static final class ClearableBufferStream
+ extends ByteArrayOutputStream {
+
+ ClearableBufferStream(int len) {
+ super(len);
+ }
+
+ byte[] getBuffer() {
+ return buf;
+ }
+
+ int length() {
+ return count;
+ }
+
+ void clear() {
+ Arrays.fill(buf, (byte) 0);
+ count = 0;
+ }
+ }
}
diff --git a/src/java.base/share/classes/sun/security/util/math/intpoly/IntegerPolynomial25519.java b/src/java.base/share/classes/sun/security/util/math/intpoly/IntegerPolynomial25519.java
index c8f23da417e..b7b1ddae0e0 100644
--- a/src/java.base/share/classes/sun/security/util/math/intpoly/IntegerPolynomial25519.java
+++ b/src/java.base/share/classes/sun/security/util/math/intpoly/IntegerPolynomial25519.java
@@ -26,6 +26,7 @@
package sun.security.util.math.intpoly;
import java.math.BigInteger;
+import jdk.internal.vm.annotation.IntrinsicCandidate;
public final class IntegerPolynomial25519 extends IntegerPolynomial {
private static final int BITS_PER_LIMB = 51;
@@ -235,6 +236,7 @@ public final class IntegerPolynomial25519 extends IntegerPolynomial {
* @param b [in] the limb operand to multiply.
* @param r [out] the product of the limbs operands that is fully reduced.
*/
+ @IntrinsicCandidate
protected void mult(long[] a, long[] b, long[] r) {
long aa0 = a[0];
long aa1 = a[1];
@@ -414,6 +416,7 @@ public final class IntegerPolynomial25519 extends IntegerPolynomial {
* @param a [in] the limb operand to square.
* @param r [out] the resulting square of the limb which is fully reduced.
*/
+ @IntrinsicCandidate
protected void square(long[] a, long[] r) {
long aa0 = a[0];
long aa1 = a[1];
diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md
index 290f729c0ca..ef99084018d 100644
--- a/src/java.base/share/man/java.md
+++ b/src/java.base/share/man/java.md
@@ -1148,8 +1148,10 @@ These `java` options control the runtime behavior of the Java HotSpot VM.
option is disabled.
[`-XX:FlightRecorderOptions=`]{#-XX_FlightRecorderOptions}*parameter*`=`*value* (or) `-XX:FlightRecorderOptions:`*parameter*`=`*value*
-: Sets the parameters that control the behavior of JFR. Multiple parameters can be specified
- by separating them with a comma.
+: Sets the parameters that control the behavior of JFR.
+ `-XX:FlightRecorderOptions:help` prints the available options, default
+ redaction filters, and example command lines. Multiple parameters can be
+ specified by separating them with a comma.
The following list contains the available JFR *parameter*`=`*value*
entries:
@@ -1196,6 +1198,43 @@ These `java` options control the runtime behavior of the Java HotSpot VM.
false, instrumentation is added when event classes are loaded. By
default, this parameter is enabled.
+ `redact-argument=`argument-filter
+ : Replace command-line arguments that match a semicolon-separated list
+ of glob patterns, for example, `*secret*;password*`. Matching is
+ case-insensitive, and the supported wildcards are `*` and `?`. To redact
+ multiple arguments, use a literal space (`' '`) as a separator.
+ For example, to match the two arguments `--auth username:token`, use the
+ filter `--auth *:*`. Filters containing spaces must be quoted as a single
+ command-line argument, for example,
+ `-XX:FlightRecorderOptions='redact-argument=--auth *:*'`.
+ Arguments containing spaces might not be matched as expected. To load
+ patterns from a file (one per line) use `@`. To add to the
+ default patterns instead of replacing them, prefix the whole list with
+ `+`, for example, `+*foo*;@redact.txt`. Use `none` (lowercase) to disable
+ all redaction filters for command-line arguments. Redacted arguments will
+ be replaced with `[REDACTED]`. The option `redact-argument` is best-effort
+ and applies only to command-line arguments in the `jdk.JVMInformation`
+ event and to the `java.command` system property in the
+ `jdk.InitialSystemProperty` event. Other events, such as `jdk.ProcessStart`
+ (child processes), are not redacted. Use `-XX:FlightRecorderOptions:help`
+ to see the default filters used by the `redact-argument` option.
+
+ `redact-key=`key-filter
+ : Replace the value of environment variables and system properties
+ whose key matches a semicolon-separated list of glob patterns,
+ for example, `*password*;*token*`. Matching is case-insensitive, and
+ the supported wildcards are `*` and `?`. To load patterns from a file
+ (one per line), use `@`. To add to the default patterns
+ instead of replacing them, prefix the whole list with `+`,
+ for example, `+*cred*;@keys.txt`. Use `none` (lowercase) to
+ disable all redaction filters for key matching. Redacted values
+ will be replaced with `[REDACTED]`. The option `redact-key` is
+ best-effort and applies only to the `jdk.InitialSystemProperty`,
+ `jdk.InitialEnvironmentVariable` and `jdk.JVMInformation` (-Dkey=...)
+ events. Other events, such as `jdk.InitialSecurityProperty`, are not
+ redacted. Use `-XX:FlightRecorderOptions:help` to see the default filters
+ used by the `redact-key` option.
+
`stackdepth=`*depth*
: Stack depth for stack traces. By default, the depth is set to 64 method
calls. The maximum is 2048. Values greater than 64 could create
@@ -2958,14 +2997,6 @@ they're used.
: Enables the use of Java Flight Recorder (JFR) during the runtime of the
application. Since JDK 8u40 this option has not been required to use JFR.
-[`-XX:+ParallelRefProcEnabled`]{#-XX__ParallelRefProcEnabled}
-: Enables parallel reference processing. By default, collectors employing multiple
- threads perform parallel reference processing if the number of parallel threads
- to use is larger than one.
- The option is available only when the throughput or G1 garbage collector is used
- (`-XX:+UseParallelGC` or `-XX:+UseG1GC`). Other collectors employing multiple
- threads always perform reference processing in parallel.
-
## Obsolete Java Options
These `java` options are still accepted but ignored, and a warning is issued
@@ -2978,6 +3009,18 @@ when they're used.
396](https://openjdk.org/jeps/396) and made obsolete in JDK 17
by [JEP 403](https://openjdk.org/jeps/403).
+## Removed Java Options
+
+These `java` options have been removed in JDK @@VERSION_SPECIFICATION@@ and using them results in an error of:
+
+> `Unrecognized VM option` *option-name*
+
+[`-XX:+AggressiveHeap`]{#-XX__AggressiveHeap}
+: Enabled Java heap optimization. This set various parameters to be
+ optimal for long-running jobs with intensive memory allocation, based on
+ the configuration of the computer (RAM and CPU). By default, the option
+ was disabled and the heap sizes configured less aggressively.
+
[`-XX:+NeverActAsServerClassMachine`]{#-XX__NeverActAsServerClassMachine}
: Enabled the "Client VM emulation" mode, which used only the C1 JIT compiler,
a 32Mb CodeCache, and the Serial GC. The maximum amount of memory that the
@@ -2998,18 +3041,18 @@ when they're used.
-XX:{+|-}UseJVMCICompiler
```
-[`-XX:+AggressiveHeap`]{#-XX__AggressiveHeap}
-: Enabled Java heap optimization. This set various parameters to be
- optimal for long-running jobs with intensive memory allocation, based on
- the configuration of the computer (RAM and CPU). By default, the option
- was disabled and the heap sizes configured less aggressively.
-
-## Removed Java Options
-
-No documented java options have been removed in JDK @@VERSION_SPECIFICATION@@.
+[`-XX:+ParallelRefProcEnabled`]{#-XX__ParallelRefProcEnabled}
+: Enables parallel reference processing. By default, collectors employing multiple
+ threads perform parallel reference processing if the number of parallel threads
+ to use is larger than one.
+ The option is available only when the throughput or G1 garbage collector is used
+ (`-XX:+UseParallelGC` or `-XX:+UseG1GC`). Other collectors employing multiple
+ threads always perform reference processing in parallel.
For the lists and descriptions of options removed in previous releases see the *Removed Java Options* section in:
+- [The `java` Command, Release 27](https://docs.oracle.com/en/java/javase/27/docs/specs/man/java.html)
+
- [The `java` Command, Release 26](https://docs.oracle.com/en/java/javase/26/docs/specs/man/java.html)
- [The `java` Command, Release 25](https://docs.oracle.com/en/java/javase/25/docs/specs/man/java.html)
diff --git a/src/java.base/windows/native/libjava/canonicalize_md.c b/src/java.base/windows/native/libjava/canonicalize_md.c
index 8596521509c..bc17531e4a5 100644
--- a/src/java.base/windows/native/libjava/canonicalize_md.c
+++ b/src/java.base/windows/native/libjava/canonicalize_md.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -181,10 +181,12 @@ WCHAR* getFinalPath(WCHAR* path, WCHAR* finalPath, DWORD size)
int isUnc = (finalPath[4] == L'U' &&
finalPath[5] == L'N' &&
finalPath[6] == L'C');
+ // keep leading double backslashes in case of UNC
+ const int startIdx = (isUnc) ? 1 : 0;
int prefixLen = (isUnc) ? 7 : 4;
// the amount to copy includes terminator
int amountToCopy = len - prefixLen + 1;
- wmemmove(finalPath, finalPath + prefixLen, amountToCopy);
+ wmemmove(finalPath + startIdx, finalPath + prefixLen, amountToCopy);
}
return finalPath;
diff --git a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java
index 2835143dc4e..f5065141816 100644
--- a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java
+++ b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -87,7 +87,9 @@ public enum SourceVersion {
* third preview)
* 26: no changes (primitive Types in Patterns, instanceof, and
* switch in fourth preview)
- * 27: tbd
+ * 27: no changes (primitive Types in Patterns, instanceof, and
+ * switch in fifth preview)
+ * 28: tbd
*/
/**
@@ -497,6 +499,18 @@ public enum SourceVersion {
* The Java Language Specification, Java SE 27 Edition
*/
RELEASE_27,
+
+ /**
+ * The version introduced by the Java Platform, Standard Edition
+ * 28.
+ *
+ * @since 28
+ *
+ * @see
+ * The Java Language Specification, Java SE 28 Edition
+ */
+ RELEASE_28,
; // Reduce code churn when appending new constants
// Note that when adding constants for newer releases, the
@@ -506,7 +520,7 @@ public enum SourceVersion {
* {@return the latest source version that can be modeled}
*/
public static SourceVersion latest() {
- return RELEASE_27;
+ return RELEASE_28;
}
private static final SourceVersion latestSupported = getLatestSupported();
@@ -521,7 +535,7 @@ public enum SourceVersion {
private static SourceVersion getLatestSupported() {
int intVersion = Runtime.version().feature();
return (intVersion >= 11) ?
- valueOf("RELEASE_" + Math.min(27, intVersion)):
+ valueOf("RELEASE_" + Math.min(28, intVersion)):
RELEASE_10;
}
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java
index bfd3b64a757..acd2d08645d 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -44,7 +44,7 @@ import javax.annotation.processing.SupportedSourceVersion;
* @see AbstractAnnotationValueVisitor9
* @since 14
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public abstract class AbstractAnnotationValueVisitor14 extends AbstractAnnotationValueVisitor9 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java
index 31d0545744a..c4257b2e6f1 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -50,7 +50,7 @@ import javax.annotation.processing.ProcessingEnvironment;
* @see AbstractAnnotationValueVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public abstract class AbstractAnnotationValueVisitorPreview extends AbstractAnnotationValueVisitor14 {
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java
index 72b796a2cb7..271b12da94f 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -50,7 +50,7 @@ import static javax.lang.model.SourceVersion.*;
* @see AbstractElementVisitor9
* @since 16
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public abstract class AbstractElementVisitor14 extends AbstractElementVisitor9 {
/**
* Constructor for concrete subclasses to call.
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java
index 009cf06a8ce..96651948d45 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -53,7 +53,7 @@ import static javax.lang.model.SourceVersion.*;
* @see AbstractElementVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public abstract class AbstractElementVisitorPreview extends AbstractElementVisitor14 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java
index 95dfc473da1..888f560035f 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -47,7 +47,7 @@ import static javax.lang.model.SourceVersion.*;
* @see AbstractTypeVisitor9
* @since 14
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public abstract class AbstractTypeVisitor14 extends AbstractTypeVisitor9 {
/**
* Constructor for concrete subclasses to call.
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java
index 257d7f5aa17..2bac66f862d 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -53,7 +53,7 @@ import static javax.lang.model.SourceVersion.*;
* @see AbstractTypeVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public abstract class AbstractTypeVisitorPreview extends AbstractTypeVisitor14 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java
index 58b1d81bcf9..3c1f36be4de 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -61,7 +61,7 @@ import javax.lang.model.SourceVersion;
* @see ElementKindVisitor9
* @since 16
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public class ElementKindVisitor14 extends ElementKindVisitor9 {
/**
* Constructor for concrete subclasses; uses {@code null} for the
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java
index e2183185b80..bca2a26ef29 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -67,7 +67,7 @@ import static javax.lang.model.SourceVersion.*;
* @see ElementKindVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public class ElementKindVisitorPreview extends ElementKindVisitor14 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java
index 6f7d8bc5fed..59aaa8ffc25 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -77,7 +77,7 @@ import static javax.lang.model.SourceVersion.*;
* @see ElementScanner9
* @since 16
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public class ElementScanner14 extends ElementScanner9 {
/**
* Constructor for concrete subclasses; uses {@code null} for the
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java
index 27080c7abc5..35ab7b49ec4 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -81,7 +81,7 @@ import static javax.lang.model.SourceVersion.*;
* @see ElementScanner14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public class ElementScannerPreview extends ElementScanner14 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java
index a5e32c936e4..9c74184af96 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -52,7 +52,7 @@ import static javax.lang.model.SourceVersion.*;
* @see SimpleAnnotationValueVisitor9
* @since 14
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public class SimpleAnnotationValueVisitor14 extends SimpleAnnotationValueVisitor9 {
/**
* Constructor for concrete subclasses; uses {@code null} for the
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java
index a98161812fe..b43226caea1 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -58,7 +58,7 @@ import static javax.lang.model.SourceVersion.*;
* @see SimpleAnnotationValueVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public class SimpleAnnotationValueVisitorPreview extends SimpleAnnotationValueVisitor14 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java
index da9797b2750..4b3e3370ab0 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -58,7 +58,7 @@ import static javax.lang.model.SourceVersion.*;
* @see SimpleElementVisitor9
* @since 16
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public class SimpleElementVisitor14 extends SimpleElementVisitor9 {
/**
* Constructor for concrete subclasses; uses {@code null} for the
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java
index 158dd24450f..3d58fb3b040 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -61,7 +61,7 @@ import static javax.lang.model.SourceVersion.*;
* @see SimpleElementVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public class SimpleElementVisitorPreview extends SimpleElementVisitor14 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java
index 07b6b2bedbe..a78fd9ae4d6 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -56,7 +56,7 @@ import static javax.lang.model.SourceVersion.*;
* @see SimpleTypeVisitor9
* @since 14
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public class SimpleTypeVisitor14 extends SimpleTypeVisitor9 {
/**
* Constructor for concrete subclasses; uses {@code null} for the
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java
index ff9a3050e12..5b0db838fbb 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -62,7 +62,7 @@ import static javax.lang.model.SourceVersion.*;
* @see SimpleTypeVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public class SimpleTypeVisitorPreview extends SimpleTypeVisitor14 {
/**
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java
index 8c80f4ad79a..c045e03dceb 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -61,7 +61,7 @@ import static javax.lang.model.SourceVersion.*;
* @see TypeKindVisitor9
* @since 14
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
public class TypeKindVisitor14 extends TypeKindVisitor9 {
/**
* Constructor for concrete subclasses to call; uses {@code null}
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java
index 62c059c605f..b23a27cd113 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -66,7 +66,7 @@ import static javax.lang.model.SourceVersion.*;
* @see TypeKindVisitor14
* @since 23
*/
-@SupportedSourceVersion(RELEASE_27)
+@SupportedSourceVersion(RELEASE_28)
@PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true)
public class TypeKindVisitorPreview extends TypeKindVisitor14 {
/**
diff --git a/src/java.desktop/share/classes/java/awt/event/MouseMotionAdapter.java b/src/java.desktop/share/classes/java/awt/event/MouseMotionAdapter.java
index 4c284bf29ba..6bec617b5ae 100644
--- a/src/java.desktop/share/classes/java/awt/event/MouseMotionAdapter.java
+++ b/src/java.desktop/share/classes/java/awt/event/MouseMotionAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -32,13 +32,13 @@ package java.awt.event;
*
* Mouse motion events occur when a mouse is moved or dragged.
* (Many such events will be generated in a normal program.
- * To track clicks and other mouse events, use the MouseAdapter.)
+ * To track clicks and other mouse events, use the {@link MouseAdapter}.)
*
* Extend this class to create a {@code MouseEvent} listener
* and override the methods for the events of interest. (If you implement the
* {@code MouseMotionListener} interface, you have to define all of
* the methods in it. This abstract class defines null methods for them
- * all, so you can only have to define methods for events you care about.)
+ * all, so you have to define only methods for events you care about.)
*