Skip to content

8350607: Consolidate MethodHandles::zero into MethodHandles::constant #23706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from

Conversation

liach
Copy link
Member

@liach liach commented Feb 20, 2025

LF editor spins classes, this avoids the spinning overhead and should speed up non-capturing lambdas too.

There may need to be additional intrinsic work for MH combinator lf bytecode generation.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8350607: Consolidate MethodHandles::zero into MethodHandles::constant (Enhancement - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/23706/head:pull/23706
$ git checkout pull/23706

Update a local copy of the PR:
$ git checkout pull/23706
$ git pull https://git.openjdk.org/jdk.git pull/23706/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 23706

View PR using the GUI difftool:
$ git pr show -t 23706

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/23706.diff

Using Webrev

Link to Webrev Comment

LF editor spins classes
@bridgekeeper
Copy link

bridgekeeper bot commented Feb 20, 2025

👋 Welcome back liach! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Feb 20, 2025

@liach This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8350607: Consolidate MethodHandles::zero into MethodHandles::constant

Reviewed-by: jrose, redestad, jvernee

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 174 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk
Copy link

openjdk bot commented Feb 20, 2025

@liach The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

Copy link
Contributor

@rose00 rose00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are on the right track. Some of the details don’t look right to me, so I left comments on the PR.

I realize it is tricky to get this stuff bootstrapped, and sometimes it helps to copy initialization code to avoid hitting a common path too soon. But I think the right answer here is probably to lean hard on common paths in BMH, for both the zero and the constant cases.

Another matter: The intrinsicData thingy bothers me. Do we really need to use it? Why not just have a clean identity NF, and then call NF(mycon) in a LF?

It looks like intrinsicData is under-used, and perhaps can be removed completely. It only carries the length of a lookup table, before this PR…? If so, I’d support lazily making N different NFs for table lookup, for each table arity in N. (I.e., N names, not one name, and no side-data.) But maybe that would be equally bad.

Anyway, those are my first reactions. Thanks!

@@ -1343,7 +1343,7 @@ enum Intrinsic {
ARRAY_STORE,
ARRAY_LENGTH,
IDENTITY,
ZERO,
CONSTANT,
NONE // no intrinsic associated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice you are using the intrinsicData property to keep track of the constant, which is a good call.

A little below here, around line 1400, there is a possible bug in the handling of intrinsicData.

Possible fix:

--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
@@ -1409,7 +1409,8 @@ static MethodHandle makeIntrinsic(MethodHandle target, Intrinsic intrinsicName)
     }
 
     static MethodHandle makeIntrinsic(MethodHandle target, Intrinsic intrinsicName, Object intrinsicData) {
-        if (intrinsicName == target.intrinsicName())
+        if (intrinsicName == target.intrinsicName() &&
+            intrinsicData == target.intrinsicData())
             return target;
         return new IntrinsicMethodHandle(target, intrinsicName, intrinsicData);
     }

It should be added to this PR, lest constants get confused somehow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the other use of intrinsicData, tableSwitch. I believe the intrinsic is actually a regression by growing the bytecode size - we should just select a MH via hash table lookup and invoke that MH, given all MHs in that list have the same type. I have removed the use of intrinsic data here and we can move on to remove it later.

I noticed that intrinsics are useful really only as part of named functions. And named functions only reuse arbitrary MHs for the invoker form. Is my understanding here correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should just select a MH via hash table lookup and invoke that MH

I had something like this in an early prototype of the tableSwitch combinator, but it does not work, as it prevents the method handle calls for each case from being inlined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this is being addressed in #23763

@liach
Copy link
Member Author

liach commented Feb 24, 2025

I think this version should be fine: Perfstartup-LambdaNoop's MethodHandle.constant instructions have decreased from 99032 to 48507 by eliding the generation of a LF via LambdaFormEditor. Tests seem to be fine too.

@liach liach changed the title Special LF treatment for MHs.constant 8350607: Consolidate MethodHandles::zero into MethodHandles::constant Feb 24, 2025
@liach liach marked this pull request as ready for review February 24, 2025 22:52
@openjdk openjdk bot added the rfr Pull request is ready for review label Feb 24, 2025
@mlbridge
Copy link

mlbridge bot commented Feb 24, 2025

Webrevs

Copy link
Member

@cl4es cl4es left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just got back home. Some comments inline, will need to run some tests and mull this over before approval.

var basicType = BasicType.basicType(type);
var form = constantForm(basicType);

if (type.isPrimitive()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could simplify this using Wrapper.forBasicType; all variants should be able to use wrapper.convert

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast is not performed if the type is an interface; this is a behavioral disparity with the old value = ptype.cast(value); in insertArguments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we'd need an explicit cast in the OBJECT case and that would be a bit redundant. Might still amount to a code simplification.

@@ -4881,7 +4870,8 @@ public static MethodHandle identity(Class<?> type) {
*/
public static MethodHandle zero(Class<?> type) {
Objects.requireNonNull(type);
return type.isPrimitive() ? zero(Wrapper.forPrimitiveType(type), type) : zero(Wrapper.OBJECT, type);
return type.isPrimitive() ? primitiveZero(Wrapper.forPrimitiveType(type))
: MethodHandleImpl.makeConstantReturning(type, null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's important but the existing impl would cache zero(Object.class) while this new impl won't. Behaviorally neutral for any other reference type, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type == Object.class can use the primitive path, but I am too lazy to add another of this check.

var species = SimpleMethodHandle.BMH_SPECIES.extendWith(type);
var carrier = argument(0, L_TYPE).withConstraint(species); // BMH bound with data
Name[] constNames = new Name[] { carrier, new Name(species.getterFunction(0), carrier) };
return LF_constant[type.ordinal()] = create(1, constNames, Kind.CONSTANT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this caching logic should be in constantForm, which also does the cache lookup.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting the field write in the create method is to help reduce the code that JIT needs to compile by reducing the getter's code size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough.

@@ -969,7 +969,7 @@ LambdaForm filterReturnForm(BasicType newType, boolean constantZero) {
if (newType == V_TYPE)
callFilter = null;
else
callFilter = new Name(constantZero(newType));
callFilter = new Name(LambdaForm.identity(newType), newType.btWrapper.zero());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could have a Name constructor that just takes an Object and returns it like this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is necessary - the majority of constant values like this are immediately sent to a NamedFunction, which already accepts such constants natively. The only case such a Name is necessary is for LF return values.

@@ -1343,7 +1343,7 @@ enum Intrinsic {
ARRAY_STORE,
ARRAY_LENGTH,
IDENTITY,
ZERO,
CONSTANT,
NONE // no intrinsic associated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should just select a MH via hash table lookup and invoke that MH

I had something like this in an early prototype of the tableSwitch combinator, but it does not work, as it prevents the method handle calls for each case from being inlined.

@@ -39,7 +39,6 @@
public class TestDynamicRegenerateHolderClasses extends DynamicArchiveTestBase {
static String CHECK_MESSAGES[] = {"java.lang.invoke.Invokers$Holder source: shared objects file (top)",
"java.lang.invoke.DirectMethodHandle$Holder source: shared objects file (top)",
"java.lang.invoke.DelegatingMethodHandle$Holder source: shared objects file (top)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this change is needed. Could you please explain it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the simplification of MethodHandles.constant, it seems invoking MethodHandles.constant no longer need to initialize DelegatingMethodHandle because there is no longer need to rebind some MethodHandle; anyways we are loading fewer classes now.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Mar 4, 2025
@liach
Copy link
Member Author

liach commented Mar 4, 2025

@cl4es @rose00 I think I have addressed your existing comments; can you review again?

Copy link
Contributor

@rose00 rose00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope that the intrinsic mechanism can be further simplified, at some point. But I defer to Jorn's comments about tableswitch.

Excellent cleanups. Lots and lots of deleted code, and other code regularized. Thank you!

@@ -32,6 +32,8 @@

/**
* A method handle whose behavior is determined only by its LambdaForm.
* Access to SimpleMethodHandle should ensure BoundMethodHandle is initialized
* first.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try factoring UNSAFE.ensureInit(BMH) into a static block in SimpleMethodHandle.java?
Sometimes that works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be exact, I did not encounter any initialization issue during my use of BMH/SimpleMH. We don't need ensureInit as SimpleMH already extends BMH. Maybe my concern is just red herring, as I saw a warning on BMH$Species_L that its access should go through BMH first.

var basicType = BasicType.basicType(type);
var form = constantForm(basicType);

if (type.isPrimitive()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we'd need an explicit cast in the OBJECT case and that would be a bit redundant. Might still amount to a code simplification.

@liach
Copy link
Member Author

liach commented Mar 7, 2025

Tier 1-3 tests only have 2 unrelated failures. Thanks for the reviews.

/integrate

@openjdk
Copy link

openjdk bot commented Mar 7, 2025

Going to push as commit 8ed6c1d.
Since your change was applied there have been 196 commits pushed to the master branch:

  • f6a8db2: 8348261: assert(n->is_Mem()) failed: memory node required
  • 5cd4fe6: 8348309: MultiNST tests need more debugging and timing
  • 7c22b81: 8350811: [JMH] test foreign.StrLenTest failed with StringIndexOutOfBoundsException for size=451
  • 54fe643: 8347433: Deprecate XML interchange in java.management/javax/management/modelmbean/DescriptorSupport for removal
  • 155697f: 8349623: [ASAN] Gtest os_linux.glibc_mallinfo_wrapper_vm fails
  • 4066f33: 8350565: NMT: remaining memory flag/type to be replaced with memory tag
  • 7314efc: 8351377: Fix the ProblemList for com/sun/management/OperatingSystemMXBean cpuLoad tests on AIX
  • 7c9a784: 8351223: Update localized resources in keytool and jarsigner
  • a23fb0a: 8348110: Update LCMS to 2.17
  • 0892913: 8351086: (fc) Make java/nio/channels/FileChannel/BlockDeviceSize.java test manual
  • ... and 186 more: https://git.openjdk.org/jdk/compare/92efab90db24a76cc28fc1ae1db870a0dd670266...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Mar 7, 2025
@openjdk openjdk bot closed this Mar 7, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Mar 7, 2025
@openjdk
Copy link

openjdk bot commented Mar 7, 2025

@liach Pushed as commit 8ed6c1d.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@@ -4900,7 +4890,7 @@ private static MethodHandle identityOrVoid(Class<?> type) {
* @param type the type of the desired method handle
* @return a constant method handle of the given type, which returns a default value of the given return type
* @throws NullPointerException if the argument is null
* @see MethodHandles#zero
* @see MethodHandles#primitiveZero

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MethodHandles​.primitiveZero(…) is a private internal API.

Suggested change
* @see MethodHandles#primitiveZero
* @see MethodHandles#zero

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a general rfe for checking inaccessible links. Don't recall why thisv was even changed, most likely ide being too smart.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs [email protected] integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

5 participants