From 522cbe9d67e31a04a7f102dd740b428e747456c9 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Mon, 1 Dec 2025 14:05:50 -0600 Subject: [PATCH 01/11] 8160821 --- .../VarHandleGuardMethodGenerator.java | 26 +----- src/hotspot/share/classfile/vmIntrinsics.hpp | 2 +- src/hotspot/share/opto/library_call.cpp | 2 +- .../classes/java/lang/invoke/Invokers.java | 2 +- .../java/lang/invoke/MethodHandleImpl.java | 11 ++- .../classes/java/lang/invoke/VarHandle.java | 24 +++++- .../VarHandleMismatchedTypeFold.java | 79 +++++++++++++++++++ 7 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/constantFold/VarHandleMismatchedTypeFold.java diff --git a/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java b/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java index 4cb833dffe2d2..dc6aaba6cbd65 100644 --- a/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java +++ b/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java @@ -122,25 +122,8 @@ final class VarHandleGuards { if (direct && handle.vform.methodType_table[ad.type] == ad.symbolicMethodTypeErased) { MethodHandle.linkToStatic(); } else { - MethodHandle mh = handle.getMethodHandle(ad.mode); - mh.asType(ad.symbolicMethodTypeInvoker).invokeBasic(); - } - }"""; - - static final String GUARD_METHOD_TEMPLATE_V = - """ - @ForceInline - @LambdaForm.Compiled - @Hidden - static final throws Throwable { - boolean direct = handle.checkAccessModeThenIsDirect(ad); - if (direct && handle.vform.methodType_table[ad.type] == ad.symbolicMethodTypeErased) { - MethodHandle.linkToStatic(); - } else if (direct && handle.vform.getMethodType_V(ad.type) == ad.symbolicMethodTypeErased) { - MethodHandle.linkToStatic(); - } else { - MethodHandle mh = handle.getMethodHandle(ad.mode); - mh.asType(ad.symbolicMethodTypeInvoker).invokeBasic(); + MethodHandle mh = ad.adaptedMethodHandle(handle); + mh.invokeBasic(); } }"""; @@ -280,10 +263,7 @@ static String generateMethod(MethodType mt) { ? "" : "\n return ad.returnType.cast(r);"; - String template = returnType == void.class - ? GUARD_METHOD_TEMPLATE_V - : GUARD_METHOD_TEMPLATE; - return template. + return GUARD_METHOD_TEMPLATE. replace("", METHOD). replace("", NAME). replaceAll("", RETURN). diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index 0895418ef848b..7bdcf47e38893 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -700,7 +700,7 @@ class methodHandle; do_signature(profileBoolean_signature, "(Z[I)Z") \ do_intrinsic(_isCompileConstant, java_lang_invoke_MethodHandleImpl, isCompileConstant_name, isCompileConstant_signature, F_S) \ do_name( isCompileConstant_name, "isCompileConstant") \ - do_alias( isCompileConstant_signature, object_boolean_signature) \ + do_alias( isCompileConstant_signature, object_int_signature) \ \ do_intrinsic(_getObjectSize, sun_instrument_InstrumentationImpl, getObjectSize_name, getObjectSize_signature, F_RN) \ do_name( getObjectSize_name, "getObjectSize0") \ diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 7a213102efd15..5f7f53b7a2b00 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -8923,7 +8923,7 @@ bool LibraryCallKit::inline_profileBoolean() { bool LibraryCallKit::inline_isCompileConstant() { Node* n = argument(0); - set_result(n->is_Con() ? intcon(1) : intcon(0)); + set_result(n->is_Con() ? intcon(1) : intcon(2)); return true; } diff --git a/src/java.base/share/classes/java/lang/invoke/Invokers.java b/src/java.base/share/classes/java/lang/invoke/Invokers.java index bd97307ac27cf..b3941b5ba5c8f 100644 --- a/src/java.base/share/classes/java/lang/invoke/Invokers.java +++ b/src/java.base/share/classes/java/lang/invoke/Invokers.java @@ -621,7 +621,7 @@ static MethodHandle getCallSiteTarget(CallSite site) { @ForceInline /*non-public*/ static void checkCustomized(MethodHandle mh) { - if (MethodHandleImpl.isCompileConstant(mh)) { + if (MethodHandleImpl.isCompileConstant(mh) != MethodHandleImpl.CONSTANT_YES) { return; // no need to customize a MH when the instance is known to JIT } if (mh.form.customized == null) { // fast approximate check that the underlying form is already customized diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index 5b8a4478be579..772a068d93cc0 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -621,11 +621,16 @@ static boolean profileBoolean(boolean result, int[] counters) { return result; } - // Intrinsified by C2. Returns true if obj is a compile-time constant. + static final int CONSTANT_PENDING = 0; + static final int CONSTANT_YES = 1; + static final int CONSTANT_NO = 2; + + // Intrinsified by C2. Returns 0 if not ready, 1 if obj is a compile-time constant, + // 2 if obj is not a compile-time constant. @Hidden @jdk.internal.vm.annotation.IntrinsicCandidate - static boolean isCompileConstant(Object obj) { - return false; + static int isCompileConstant(Object obj) { + return CONSTANT_PENDING; } static MethodHandle makeGuardWithTest(MethodHandle test, diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index 9246fdc039571..d51070acaf84b 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -2016,8 +2016,11 @@ static final class AccessDescriptor { final Class returnType; final int type; final int mode; + // The cache for adapted MH if the access site has a constant VH + // that does not match the erased type for. + @Stable MethodHandle adaptedMh; - public AccessDescriptor(MethodType symbolicMethodType, int type, int mode) { + AccessDescriptor(MethodType symbolicMethodType, int type, int mode) { this.symbolicMethodTypeExact = symbolicMethodType; this.symbolicMethodTypeErased = symbolicMethodType.erase(); this.symbolicMethodTypeInvoker = symbolicMethodType.insertParameterTypes(0, VarHandle.class); @@ -2025,6 +2028,23 @@ public AccessDescriptor(MethodType symbolicMethodType, int type, int mode) { this.type = type; this.mode = mode; } + + @ForceInline + MethodHandle adaptedMethodHandle(VarHandle vh) { + var constant = MethodHandleImpl.isCompileConstant(vh); + var cache = adaptedMh; + if (constant == MethodHandleImpl.CONSTANT_YES && cache != null) { + return cache; + } + + // This is still a hot path if vh is not constant - in this case, + // asType is the bottleneck for constant folding, unfortunately + var result = vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker); + if (constant == MethodHandleImpl.CONSTANT_PENDING && cache == null) { + adaptedMh = result; + } + return result; + } } /** diff --git a/test/hotspot/jtreg/compiler/c2/irTests/constantFold/VarHandleMismatchedTypeFold.java b/test/hotspot/jtreg/compiler/c2/irTests/constantFold/VarHandleMismatchedTypeFold.java new file mode 100644 index 0000000000000..2530bded4f6b2 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/constantFold/VarHandleMismatchedTypeFold.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.irTests.constantFold; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + +import compiler.lib.ir_framework.Check; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; + +/* + * @test + * @bug 8160821 + * @summary Verify constant folding is possible for mismatched VarHandle access + * @library /test/lib / + * @requires vm.compiler2.enabled + * @run driver compiler.c2.irTests.constantFold.VarHandleMismatchedTypeFold + */ +public class VarHandleMismatchedTypeFold { + + public static void main(String[] args) { + TestFramework.runWithFlags( + "-XX:+UnlockExperimentalVMOptions" + ); + } + + static final int a = 5; + + static final VarHandle vh; + + static { + try { + vh = MethodHandles.lookup().findStaticVarHandle(VarHandleMismatchedTypeFold.class, + "a", int.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Test + @IR(failOn = {IRNode.ADD_L, IRNode.LOAD_L}) + public long testSum() { + return 2L + (long) vh.get(); + } + + @Check(test = "testSum") + public void runTestSum() { + long sum = testSum(); + if (sum != 2L + 5L) { + throw new IllegalStateException("Failed, unexpected sum " + sum); + } + } + +} From 886d3918a35860592be7619aaeaaa25568c67c86 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Mon, 1 Dec 2025 14:29:57 -0600 Subject: [PATCH 02/11] Logical fallacy --- src/java.base/share/classes/java/lang/invoke/Invokers.java | 2 +- src/java.base/share/classes/java/lang/invoke/VarHandle.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/Invokers.java b/src/java.base/share/classes/java/lang/invoke/Invokers.java index b3941b5ba5c8f..a5347c3458d77 100644 --- a/src/java.base/share/classes/java/lang/invoke/Invokers.java +++ b/src/java.base/share/classes/java/lang/invoke/Invokers.java @@ -621,7 +621,7 @@ static MethodHandle getCallSiteTarget(CallSite site) { @ForceInline /*non-public*/ static void checkCustomized(MethodHandle mh) { - if (MethodHandleImpl.isCompileConstant(mh) != MethodHandleImpl.CONSTANT_YES) { + if (MethodHandleImpl.isCompileConstant(mh) == MethodHandleImpl.CONSTANT_YES) { return; // no need to customize a MH when the instance is known to JIT } if (mh.form.customized == null) { // fast approximate check that the underlying form is already customized diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index d51070acaf84b..11f011251f5fa 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.java @@ -2040,7 +2040,7 @@ MethodHandle adaptedMethodHandle(VarHandle vh) { // This is still a hot path if vh is not constant - in this case, // asType is the bottleneck for constant folding, unfortunately var result = vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker); - if (constant == MethodHandleImpl.CONSTANT_PENDING && cache == null) { + if (constant != MethodHandleImpl.CONSTANT_NO && cache == null) { adaptedMh = result; } return result; From 7bcdcbf3d6122aeb82b6ecbd07b6600624b4c066 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Mon, 1 Dec 2025 17:36:47 -0600 Subject: [PATCH 03/11] Tweak VH usage in some classes --- .../compiler/c2/irTests/TestGetAndAdd.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java b/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java index 905a11eb49457..61b49aea094f4 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java @@ -29,7 +29,7 @@ /* * @test - * bug 8308444 + * @bug 8308444 * @summary verify that the correct node is matched for atomic getAndAdd * @requires os.simpleArch == "x64" * @requires vm.compiler2.enabled @@ -75,8 +75,8 @@ public static void main(String[] args) { @IR(counts = {IRNode.X86_LOCK_ADDB_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDB, "3"}, phase = CompilePhase.FINAL_CODE) public static void addB() { - B.getAndAdd(b2); - B.getAndAdd((byte)1); + var _ = (byte) B.getAndAdd(b2); + var _ = (byte) B.getAndAdd((byte)1); b2 = (byte)B.getAndAdd(b2); } @@ -85,8 +85,8 @@ public static void addB() { @IR(counts = {IRNode.X86_LOCK_ADDS_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDS, "3"}, phase = CompilePhase.FINAL_CODE) public static void addS() { - S.getAndAdd(s2); - S.getAndAdd((short)1); + var _ = (short) S.getAndAdd(s2); + var _ = (short) S.getAndAdd((short)1); s2 = (short)S.getAndAdd(s2); } @@ -95,8 +95,8 @@ public static void addS() { @IR(counts = {IRNode.X86_LOCK_ADDI_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDI, "3"}, phase = CompilePhase.FINAL_CODE) public static void addI() { - I.getAndAdd(i2); - I.getAndAdd(1); + var _ = (int) I.getAndAdd(i2); + var _ = (int) I.getAndAdd(1); i2 = (int)I.getAndAdd(i2); } @@ -105,8 +105,8 @@ public static void addI() { @IR(counts = {IRNode.X86_LOCK_ADDL_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDL, "3"}, phase = CompilePhase.FINAL_CODE) public static void addL() { - L.getAndAdd(l2); - L.getAndAdd(1L); + var _ = (long) L.getAndAdd(l2); + var _ = (long) L.getAndAdd(1L); l2 = (long)L.getAndAdd(l2); } } From b8039d658f9bc01f71e80a2194c630837fd92b39 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 2 Dec 2025 11:24:00 -0600 Subject: [PATCH 04/11] Review tweaks --- .../share/classes/java/lang/invoke/VarHandle.java | 10 ++++++---- .../bench/java/lang/invoke/VarHandleExact.java | 13 +++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index 11f011251f5fa..a4923e6846a96 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.java @@ -2032,15 +2032,17 @@ static final class AccessDescriptor { @ForceInline MethodHandle adaptedMethodHandle(VarHandle vh) { var constant = MethodHandleImpl.isCompileConstant(vh); - var cache = adaptedMh; - if (constant == MethodHandleImpl.CONSTANT_YES && cache != null) { - return cache; + if (constant == MethodHandleImpl.CONSTANT_YES) { + var cache = adaptedMh; + if (cache != null) { + return cache; + } } // This is still a hot path if vh is not constant - in this case, // asType is the bottleneck for constant folding, unfortunately var result = vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker); - if (constant != MethodHandleImpl.CONSTANT_NO && cache == null) { + if (constant != MethodHandleImpl.CONSTANT_NO) { adaptedMh = result; } return result; diff --git a/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java b/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java index 1b5e4f776011d..1a56fdc4d6a1c 100644 --- a/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java +++ b/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -69,16 +69,21 @@ public void setup() { @Benchmark public void exact_exactInvocation() { - exact.set(data, (long) 42); + var _ = (long) exact.getAndAdd(data, (long) 42); } @Benchmark public void generic_genericInvocation() { - generic.set(data, 42); + var _ = (long) generic.getAndAdd(data, 42); + } + + @Benchmark + public void generic_returnDroppingInvocation() { + generic.getAndAdd(data, (long) 42); } @Benchmark public void generic_exactInvocation() { - generic.set(data, (long) 42); + var _ = (long) generic.getAndAdd(data, (long) 42); } } From 7ca6a5ce7d389639a194acb1e4e7774d8533b056 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 2 Dec 2025 16:33:01 -0600 Subject: [PATCH 05/11] Stage --- src/hotspot/share/classfile/vmIntrinsics.hpp | 2 +- src/hotspot/share/opto/library_call.cpp | 2 +- .../classes/java/lang/invoke/Invokers.java | 2 +- .../java/lang/invoke/MethodHandleImpl.java | 13 +++++------- .../classes/java/lang/invoke/VarHandle.java | 21 +++++++------------ 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index 7bdcf47e38893..43198ead573ef 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -700,7 +700,7 @@ class methodHandle; do_signature(profileBoolean_signature, "(Z[I)Z") \ do_intrinsic(_isCompileConstant, java_lang_invoke_MethodHandleImpl, isCompileConstant_name, isCompileConstant_signature, F_S) \ do_name( isCompileConstant_name, "isCompileConstant") \ - do_alias( isCompileConstant_signature, object_int_signature) \ + do_alias( isCompileConstant_signature, object_boolean_signature) \ \ do_intrinsic(_getObjectSize, sun_instrument_InstrumentationImpl, getObjectSize_name, getObjectSize_signature, F_RN) \ do_name( getObjectSize_name, "getObjectSize0") \ diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 5f7f53b7a2b00..7a213102efd15 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -8923,7 +8923,7 @@ bool LibraryCallKit::inline_profileBoolean() { bool LibraryCallKit::inline_isCompileConstant() { Node* n = argument(0); - set_result(n->is_Con() ? intcon(1) : intcon(2)); + set_result(n->is_Con() ? intcon(1) : intcon(0)); return true; } diff --git a/src/java.base/share/classes/java/lang/invoke/Invokers.java b/src/java.base/share/classes/java/lang/invoke/Invokers.java index a5347c3458d77..bd97307ac27cf 100644 --- a/src/java.base/share/classes/java/lang/invoke/Invokers.java +++ b/src/java.base/share/classes/java/lang/invoke/Invokers.java @@ -621,7 +621,7 @@ static MethodHandle getCallSiteTarget(CallSite site) { @ForceInline /*non-public*/ static void checkCustomized(MethodHandle mh) { - if (MethodHandleImpl.isCompileConstant(mh) == MethodHandleImpl.CONSTANT_YES) { + if (MethodHandleImpl.isCompileConstant(mh)) { return; // no need to customize a MH when the instance is known to JIT } if (mh.form.customized == null) { // fast approximate check that the underlying form is already customized diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index 772a068d93cc0..02e3b7e233beb 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -621,16 +621,13 @@ static boolean profileBoolean(boolean result, int[] counters) { return result; } - static final int CONSTANT_PENDING = 0; - static final int CONSTANT_YES = 1; - static final int CONSTANT_NO = 2; - - // Intrinsified by C2. Returns 0 if not ready, 1 if obj is a compile-time constant, - // 2 if obj is not a compile-time constant. + // Intrinsified by C2. Returns true if obj is a compile-time constant. + // Note that a non-constant value may be subsequently promoted to a constant, + // so a false return value does not indicate obj is definitely not a constant. @Hidden @jdk.internal.vm.annotation.IntrinsicCandidate - static int isCompileConstant(Object obj) { - return CONSTANT_PENDING; + static boolean isCompileConstant(Object obj) { + return false; } static MethodHandle makeGuardWithTest(MethodHandle test, diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index a4923e6846a96..c7792299359f9 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.java @@ -2016,9 +2016,10 @@ static final class AccessDescriptor { final Class returnType; final int type; final int mode; - // The cache for adapted MH if the access site has a constant VH - // that does not match the erased type for. - @Stable MethodHandle adaptedMh; + // The cache for last adapted MH if the access site has a VH that does + // not match the erased type for. It can be safely reused if VH is + // discovered to be a constant during C2 compilation. + @Stable MethodHandle lastAdaption; AccessDescriptor(MethodType symbolicMethodType, int type, int mode) { this.symbolicMethodTypeExact = symbolicMethodType; @@ -2031,21 +2032,15 @@ static final class AccessDescriptor { @ForceInline MethodHandle adaptedMethodHandle(VarHandle vh) { - var constant = MethodHandleImpl.isCompileConstant(vh); - if (constant == MethodHandleImpl.CONSTANT_YES) { - var cache = adaptedMh; + if (MethodHandleImpl.isCompileConstant(vh)) { + var cache = lastAdaption; if (cache != null) { return cache; } } - // This is still a hot path if vh is not constant - in this case, - // asType is the bottleneck for constant folding, unfortunately - var result = vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker); - if (constant != MethodHandleImpl.CONSTANT_NO) { - adaptedMh = result; - } - return result; + // Keep capturing - vh may suddenly get promoted to a constant by C2 + return lastAdaption = vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker); } } From 80237bcbe128a0fd15913dc71f95ab0adf76c080 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 2 Dec 2025 16:34:11 -0600 Subject: [PATCH 06/11] Redundant change --- src/hotspot/share/classfile/vmIntrinsics.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index 43198ead573ef..0895418ef848b 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -700,7 +700,7 @@ class methodHandle; do_signature(profileBoolean_signature, "(Z[I)Z") \ do_intrinsic(_isCompileConstant, java_lang_invoke_MethodHandleImpl, isCompileConstant_name, isCompileConstant_signature, F_S) \ do_name( isCompileConstant_name, "isCompileConstant") \ - do_alias( isCompileConstant_signature, object_boolean_signature) \ + do_alias( isCompileConstant_signature, object_boolean_signature) \ \ do_intrinsic(_getObjectSize, sun_instrument_InstrumentationImpl, getObjectSize_name, getObjectSize_signature, F_RN) \ do_name( getObjectSize_name, "getObjectSize0") \ From d49ad129ab30150de424e8372364fa227ec49eb4 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 2 Dec 2025 16:42:57 -0600 Subject: [PATCH 07/11] Rollback getAndAdd for now --- .../jtreg/compiler/c2/irTests/TestGetAndAdd.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java b/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java index 61b49aea094f4..7b9273d04a688 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java @@ -75,8 +75,8 @@ public static void main(String[] args) { @IR(counts = {IRNode.X86_LOCK_ADDB_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDB, "3"}, phase = CompilePhase.FINAL_CODE) public static void addB() { - var _ = (byte) B.getAndAdd(b2); - var _ = (byte) B.getAndAdd((byte)1); + B.getAndAdd(b2); + B.getAndAdd((byte)1); b2 = (byte)B.getAndAdd(b2); } @@ -85,8 +85,8 @@ public static void addB() { @IR(counts = {IRNode.X86_LOCK_ADDS_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDS, "3"}, phase = CompilePhase.FINAL_CODE) public static void addS() { - var _ = (short) S.getAndAdd(s2); - var _ = (short) S.getAndAdd((short)1); + S.getAndAdd(s2); + S.getAndAdd((short)1); s2 = (short)S.getAndAdd(s2); } @@ -95,8 +95,8 @@ public static void addS() { @IR(counts = {IRNode.X86_LOCK_ADDI_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDI, "3"}, phase = CompilePhase.FINAL_CODE) public static void addI() { - var _ = (int) I.getAndAdd(i2); - var _ = (int) I.getAndAdd(1); + I.getAndAdd(i2); + I.getAndAdd(1); i2 = (int)I.getAndAdd(i2); } @@ -105,8 +105,8 @@ public static void addI() { @IR(counts = {IRNode.X86_LOCK_ADDL_IMM, "1"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {IRNode.X86_LOCK_XADDL, "3"}, phase = CompilePhase.FINAL_CODE) public static void addL() { - var _ = (long) L.getAndAdd(l2); - var _ = (long) L.getAndAdd(1L); + L.getAndAdd(l2); + L.getAndAdd(1L); l2 = (long)L.getAndAdd(l2); } } From 89e21b4b9ecfcc026a4f3abbe416488c21f63e47 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 3 Dec 2025 09:49:08 -0600 Subject: [PATCH 08/11] Fix problem identified by Jorn --- .../classes/java/lang/invoke/VarHandle.java | 40 ++++++++++++++----- .../java/lang/foreign/VarHandleExact.java | 11 +++-- .../java/lang/invoke/VarHandleExact.java | 2 +- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index c7792299359f9..dcd6efc3928de 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.java @@ -2016,10 +2016,24 @@ static final class AccessDescriptor { final Class returnType; final int type; final int mode; - // The cache for last adapted MH if the access site has a VH that does - // not match the erased type for. It can be safely reused if VH is - // discovered to be a constant during C2 compilation. - @Stable MethodHandle lastAdaption; + + // Adaption mechanism to reduce overhead for non-exact access. + // This heuristic assumes that each sigpoly VH call site usually sees + // exactly one VarHandle instance. Each sigpoly VH call site already + // has a dedicated AccessDescriptor. + // (See MethodHandleNatives::varHandleOperationLinkerMethod) + // However, for correctness, we must verify the incoming VarHandle; + // adaptedMethodHandle may be inlined by different callers. + // In the long run, we wish to put a specific-type invoker that converts + // from one fixed type (symbolicMethodTypeInvoker) to another (the + // invocation type of the underlying MemberName, or MH for indirect VH), + // perform a foldable lookup with a hash table, and hope C2 inline it + // all. + + // Object indirection is the only way to ensure the vh and mh are not + // from two writes (they must not be tearable) + private record Adaption(VarHandle vh, MethodHandle mh) {} + private @Stable Adaption adaption; AccessDescriptor(MethodType symbolicMethodType, int type, int mode) { this.symbolicMethodTypeExact = symbolicMethodType; @@ -2032,15 +2046,19 @@ static final class AccessDescriptor { @ForceInline MethodHandle adaptedMethodHandle(VarHandle vh) { - if (MethodHandleImpl.isCompileConstant(vh)) { - var cache = lastAdaption; - if (cache != null) { - return cache; - } + var cache = adaption; + if (cache != null && cache.vh == vh) { + return cache.mh; } - // Keep capturing - vh may suddenly get promoted to a constant by C2 - return lastAdaption = vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker); + var mh = vh.getMethodHandle(mode).asType(symbolicMethodTypeInvoker); + if (cache == null) { + // Reduce costly object allocation - if our assumption stands, + // the first adaption works, and we don't want allocations for + // every VH invocation. + adaption = new Adaption(vh, mh); + } + return mh; } } diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java b/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java index b1e9bdf298784..ec3222535037b 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java @@ -74,16 +74,21 @@ public void tearDown() { @Benchmark public void exact_exactInvocation() { - exact.set(data, (long) 0, 42); + var _ = (int) exact.getAndAdd(data, (long) 0, 42); } @Benchmark public void generic_genericInvocation() { - generic.set(data, 0, 42); + generic.getAndAdd(data, 0, 42); + } + + @Benchmark + public void generic_returnDroppingInvocation() { + generic.getAndAdd(data, (long) 0, 42); } @Benchmark public void generic_exactInvocation() { - generic.set(data, (long) 0, 42); + var _ = (int) generic.getAndAdd(data, (long) 0, 42); } } diff --git a/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java b/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java index 1a56fdc4d6a1c..35714130bf0bb 100644 --- a/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java +++ b/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleExact.java @@ -74,7 +74,7 @@ public void exact_exactInvocation() { @Benchmark public void generic_genericInvocation() { - var _ = (long) generic.getAndAdd(data, 42); + generic.getAndAdd(data, 42); } @Benchmark From dd76a640aa1aeeaceeb23433e10e69530427e8ed Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 3 Dec 2025 09:55:27 -0600 Subject: [PATCH 09/11] Copyright years --- test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java | 2 +- .../org/openjdk/bench/java/lang/foreign/VarHandleExact.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java b/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java index 7b9273d04a688..50b1abe12678c 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestGetAndAdd.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java b/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java index ec3222535037b..204a723d8310c 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it From ff7b36297c7f12020970ca048eded2b04fcf797c Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 3 Dec 2025 10:39:12 -0600 Subject: [PATCH 10/11] Test from Jorn --- .../PolymorphicCallSiteInlineTest.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 test/jdk/java/lang/invoke/VarHandles/PolymorphicCallSiteInlineTest.java diff --git a/test/jdk/java/lang/invoke/VarHandles/PolymorphicCallSiteInlineTest.java b/test/jdk/java/lang/invoke/VarHandles/PolymorphicCallSiteInlineTest.java new file mode 100644 index 0000000000000..568d132f2840b --- /dev/null +++ b/test/jdk/java/lang/invoke/VarHandles/PolymorphicCallSiteInlineTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8160821 + * @summary Ensures a polymorphic call site with non-exact invocation won't + * be incorrectly inlined/optimized + * @requires vm.compiler2.enabled + * @run main/othervm -XX:CompileCommand=PrintCompilation,Main::* + * -XX:CompileCommand=dontinline,Main::payload* + * -Xbatch + * PolymorphicCallSiteInlineTest + */ + +import java.lang.invoke.*; +import java.util.concurrent.CountDownLatch; + +public class PolymorphicCallSiteInlineTest { + + // C2 should inline m into payload1/payload2 in this many runs + static final int RUNS = 0x10000; + + static final int X = 0; + static final long Y = 0L; + + static final VarHandle VH_X; + static final VarHandle VH_Y; + + static { + try { + var lookup = MethodHandles.lookup(); + VH_X = lookup.findStaticVarHandle(PolymorphicCallSiteInlineTest.class, "X", int.class); + VH_Y = lookup.findStaticVarHandle(PolymorphicCallSiteInlineTest.class, "Y", long.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + }; + + public static void main(String[] args) { + + CountDownLatch latch = new CountDownLatch(2); + + Thread.ofPlatform().start(() -> { + latch.countDown(); + try { + latch.await(); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + System.out.println("T1 running"); + for (int i = 0; i < RUNS; i++) { + payload1(); + } + }); + + Thread.ofPlatform().start(() -> { + latch.countDown(); + try { + latch.await(); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + System.out.println("T2 running"); + for (int i = 0; i < RUNS; i++) { + payload2(); + } + }); + } + + public static int payload1() { + return (int) m(VH_X); + } + + public static long payload2() { + return (long) m(VH_Y); + } + + public static Object m(VarHandle vh) { + // This is a polymorphic site that sees many VarHandle, but each VH + // is considered "constant" when inlined into payload1/payload2 + // payload1/payload2 will throw exceptions if the incorrect VH gets inlined + return vh.get(); + } +} From 8200fb283ba2a25b427fe08c97d7a2021f08dba7 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 3 Dec 2025 19:42:56 -0600 Subject: [PATCH 11/11] Revert void special case removal due to C2 shortage causing TestZGCBarrierElision::testAtomicThenAtomicAnotherField failure --- .../VarHandleGuardMethodGenerator.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java b/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java index dc6aaba6cbd65..4cb833dffe2d2 100644 --- a/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java +++ b/make/jdk/src/classes/build/tools/methodhandle/VarHandleGuardMethodGenerator.java @@ -122,8 +122,25 @@ final class VarHandleGuards { if (direct && handle.vform.methodType_table[ad.type] == ad.symbolicMethodTypeErased) { MethodHandle.linkToStatic(); } else { - MethodHandle mh = ad.adaptedMethodHandle(handle); - mh.invokeBasic(); + MethodHandle mh = handle.getMethodHandle(ad.mode); + mh.asType(ad.symbolicMethodTypeInvoker).invokeBasic(); + } + }"""; + + static final String GUARD_METHOD_TEMPLATE_V = + """ + @ForceInline + @LambdaForm.Compiled + @Hidden + static final throws Throwable { + boolean direct = handle.checkAccessModeThenIsDirect(ad); + if (direct && handle.vform.methodType_table[ad.type] == ad.symbolicMethodTypeErased) { + MethodHandle.linkToStatic(); + } else if (direct && handle.vform.getMethodType_V(ad.type) == ad.symbolicMethodTypeErased) { + MethodHandle.linkToStatic(); + } else { + MethodHandle mh = handle.getMethodHandle(ad.mode); + mh.asType(ad.symbolicMethodTypeInvoker).invokeBasic(); } }"""; @@ -263,7 +280,10 @@ static String generateMethod(MethodType mt) { ? "" : "\n return ad.returnType.cast(r);"; - return GUARD_METHOD_TEMPLATE. + String template = returnType == void.class + ? GUARD_METHOD_TEMPLATE_V + : GUARD_METHOD_TEMPLATE; + return template. replace("", METHOD). replace("", NAME). replaceAll("", RETURN).