Skip to content

Conversation

@eme64
Copy link
Contributor

@eme64 eme64 commented Mar 25, 2025

We should extend the functionality of Verify.checkEQ:

  • Allow different NaN encodings to be seen as equal (by default).
  • Compare VectorAPI vectors.
  • Compare Exceptions, and their messages.
  • Compare arbitrary Objects via Reflection.

Note: this is a prerequisite for the Template Library JDK-8352861 / #23418.


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-8352869: Verify.checkEQ: extension for NaN, VectorAPI and arbitrary Objects (Enhancement - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 24224

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

Using diff file

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

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Mar 25, 2025

👋 Welcome back epeter! 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 Mar 25, 2025

@eme64 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:

8352869: Verify.checkEQ: extension for NaN, VectorAPI and arbitrary Objects

Reviewed-by: chagedorn, thartmann

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 14 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 openjdk bot changed the title JDK-8352869 8352869: Verify.checkEQ: extension for NaN, VectorAPI and arbitrary Objects Mar 25, 2025
@openjdk
Copy link

openjdk bot commented Mar 25, 2025

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

  • hotspot-compiler

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.

@eme64 eme64 marked this pull request as ready for review March 25, 2025 11:22
@openjdk openjdk bot added the rfr Pull request is ready for review label Mar 25, 2025
@mlbridge
Copy link

mlbridge bot commented Mar 25, 2025

*/
public static void checkEQ(Object a, Object b) {
checkEQ(a, b, "");
public static void checkEQ(Object a, Object b, boolean isFloatCheckWithRawBits, boolean isCheckWithArbitraryClasses) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a suggestion. One boolean might be ok, but once you start adding 2 booleans it seems like a bit of a code smell to me. Do you envision more options being added? I would personally create a VerifyOptions record with the boolean flags options and pass that in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll experiment with a VerifyOptions, thanks for the suggestion!

*/
private void checkEQimpl(float a, float b, String field, Object aParent, Object bParent) {
if (isFloatEQ(a, b)) {
System.err.println("ERROR: Verify.checkEQ failed: value mismatch. check raw: " + isFloatCheckWithRawBits);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would using a text block here make it more readable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@galderz What do you mean by a text block? String Templates would be nice, but we don't have them. Do you mean I should use String.format? But there is always so tricky to know what is going to be formatted to where...

System.err.println("ERROR: Verify.checkEQ failed: value mismatch for " + context);
private void checkEQimpl(double a, double b, String field, Object aParent, Object bParent) {
if (isDoubleEQ(a, b)) {
System.err.println("ERROR: Verify.checkEQ failed: value mismatch. check raw: " + isFloatCheckWithRawBits);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same text block comment here

}

private void print(Object a, Object b, String field, Object aParent, Object bParent) {
System.err.println(" aParent: " + aParent);
Copy link
Contributor

Choose a reason for hiding this comment

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

Text block?

} catch (VerifyException e) {}
}

public static void checkNE(Object a, Object b) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If checkEQ lives in Verify, wouldn't it make sense to also have checkNE there? Seems like the natural place making the API symmetric.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest, I don't see the need for it being symmetric, i.e. for the API to have a checkNE. This checkNE method is just a convenience function to check that an exception is thrown if I expect it to.

@eme64
Copy link
Contributor Author

eme64 commented Mar 28, 2025

@galderz Thanks for the comments! I think I addressed / answered all of them. Could you please have another look? :)

*/
private void checkEQimpl(float a, float b, String field, Object aParent, Object bParent) {
if (isFloatEQ(a, b)) {
System.err.println("ERROR: Verify.checkEQ failed: value mismatch. check raw: " + verifyOptions.isFloatCheckWithRawBits);
Copy link
Contributor

Choose a reason for hiding this comment

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

I meant using JEP 378 text blocks, e.g.

Suggested change
System.err.println("ERROR: Verify.checkEQ failed: value mismatch. check raw: " + verifyOptions.isFloatCheckWithRawBits);
System.err.printf("""
ERROR: Verify.checkEQ failed: value mismatch. check raw: %b
Values: %.1f vs %.1f
Raw: %d vs %d
""", isFloatCheckWithRawBits, a, b, Float.floatToRawIntBits(a), Float.floatToRawIntBits(b));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see.

That has advantages and disadvantages.
Advantage: You can more easily see the "skeleton" of the test.
Disadvantage: Mapping the "holes" and the "values" is annoying, you basically have to count through each position. Plus it may end up being more lines.

Best would really be String Templates.

I asked @chhagedorn , he does not have an opinion either way.

Personally, I prefer my way, where you can easily see what values go where directly. But this is probably a taste question.

Copy link
Member

@chhagedorn chhagedorn left a comment

Choose a reason for hiding this comment

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

Nice extensions! Some initial comments.

*/

package compiler.lib.verify;

Copy link
Member

Choose a reason for hiding this comment

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

You should update the copyright year.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done :)

Comment on lines 202 to 209
if (verifyOptions.isCheckWithArbitraryClasses) {
checkEQArbitraryClasses(a, b);
return;
} else {
System.err.println("ERROR: Verify.checkEQ failed: type not supported: " + ca.getName());
print(a, b, field, aParent, bParent);
throw new VerifyException("Object type not supported: " + ca.getName() + " -- did you mean to 'enableCheckWithArbitraryClasses'?");
}
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason behind throwing instead of just comparing two arbitrary objects by default? If a user calls Verify.checkEQ() and sees this exception, I would guess he then just passes the additional option and we have the same result. But maybe I'm missing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. I think my reasoning was that comparing arbitrary classes requires reflection. And that is rather slow. So by default it would be good if that feature is not enabled, so the user tries to avoid it, and is aware when they enable it explicitly.

But if you think that is not useful, I can remove the feature.

@chhagedorn what do you think?

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 the intention to let the user double check is good. I'm not sure though if the user is really aware of the potential slow down without diving deeper into the implementation. All they know is that checkEQ somehow does not support their some objects but there is a simple workaround to still use it. So, the real question is: How many users will then consider doing something different when facing this exception and not just enable it anyway? I guess enabling is probably the most natural thing to do.

Given that, I would probably just drop this. It would also simplify the API usage in the following way:

We would only have checks with NaNs being all equals and comparing raw bits (i.e. NaNs not equal). Then you could offer checkEQ() (default) and checkRawBitsEQ() or something like that. Then users do not need to worry about creating and passing in an Options.

What do you think about these suggestions?

What we could do either way at the checkEQ() API method: Describe the potential slow down with reflection when not using certain classes.

eme64 and others added 2 commits April 1, 2025 06:57
@eme64
Copy link
Contributor Author

eme64 commented Apr 1, 2025

@chhagedorn Thanks for the suggestions and questions! I think I addressed them all :)

Copy link
Member

@chhagedorn chhagedorn left a comment

Choose a reason for hiding this comment

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

I'll have a closer look at the code later again :-)

Comment on lines 202 to 209
if (verifyOptions.isCheckWithArbitraryClasses) {
checkEQArbitraryClasses(a, b);
return;
} else {
System.err.println("ERROR: Verify.checkEQ failed: type not supported: " + ca.getName());
print(a, b, field, aParent, bParent);
throw new VerifyException("Object type not supported: " + ca.getName() + " -- did you mean to 'enableCheckWithArbitraryClasses'?");
}
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 the intention to let the user double check is good. I'm not sure though if the user is really aware of the potential slow down without diving deeper into the implementation. All they know is that checkEQ somehow does not support their some objects but there is a simple workaround to still use it. So, the real question is: How many users will then consider doing something different when facing this exception and not just enable it anyway? I guess enabling is probably the most natural thing to do.

Given that, I would probably just drop this. It would also simplify the API usage in the following way:

We would only have checks with NaNs being all equals and comparing raw bits (i.e. NaNs not equal). Then you could offer checkEQ() (default) and checkRawBitsEQ() or something like that. Then users do not need to worry about creating and passing in an Options.

What do you think about these suggestions?

What we could do either way at the checkEQ() API method: Describe the potential slow down with reflection when not using certain classes.

@eme64
Copy link
Contributor Author

eme64 commented Apr 2, 2025

@chhagedorn Ok, I refactored it. I'm now always comparing arbitrary classes. And checkEQWithRawBits does the comparison with raw bits, no Options required any more. Added a comment about reflection making things slow.

Copy link
Member

@chhagedorn chhagedorn left a comment

Choose a reason for hiding this comment

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

Thanks for the update! It's much easier to use and understand now I think.

I did a complete pass and left a lot of comments but mostly minor things. Overall, I think this looks great! :-)

Comment on lines +59 to +60
private final HashMap<Object, Object> a2b = new HashMap<>();
private final HashMap<Object, Object> b2a = new HashMap<>();
Copy link
Member

@chhagedorn chhagedorn Apr 2, 2025

Choose a reason for hiding this comment

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

Can you add a comment here what a2b and b2a means? See also some other comment further down about a2b/b2a, maybe you can share some docs or cross reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added some documentation :)


private void print(Object a, Object b, String field, Object aParent, Object bParent) {
System.err.println(" aParent: " + aParent);
System.err.println(" bParent: " + bParent);
Copy link
Member

Choose a reason for hiding this comment

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

Should we print null parents or just skip them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it does not hurt to print null here. It makes the code a little simpler.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, maybe we can print <none> in case of a null for more clarity?

Object bPrevious = a2b.get(a);
Object aPrevious = b2a.get(b);
if (aPrevious == null && bPrevious == null) {
// Record for next time.
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain, maybe as comment at checkAlreadyVisited(), why we want to have these caches?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added documentation :)

private void printMemorySegmentValue(MemorySegment a, long offset, int range) {
long start = Long.max(offset - range, 0);
long end = Long.min(offset + range, a.byteSize());
for (long i = start; i < end; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit below: You can replace System.err.println("") with System.err.println().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

Co-authored-by: Christian Hagedorn <[email protected]>
@openjdk openjdk bot removed the rfr Pull request is ready for review label Apr 3, 2025
@eme64
Copy link
Contributor Author

eme64 commented Apr 3, 2025

@chhagedorn Thanks for the thorough review :)
I think I addressed all your comments 😊

Copy link
Member

@chhagedorn chhagedorn left a comment

Choose a reason for hiding this comment

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

Thanks for addressing all my comments and doing the updates! I have some more final comments but then I think it's good to go from my side!

private void checkEQimpl(char a, char b, String field, Object aParent, Object bParent) {
if (a != b) {
System.err.println("ERROR: Verify.checkEQ failed: value mismatch: " + (int)a + " vs " + (int)b + " for " + context);
System.err.println("ERROR: Verify.checkEQ failed: value mismatch: " + (int)a + " vs " + (int)b);
Copy link
Member

Choose a reason for hiding this comment

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

Right, that makes sense for the char case. But good that we could remove it for the short case.

*/
private void checkEQimpl(float a, float b, String field, Object aParent, Object bParent) {
if (isFloatEQ(a, b)) {
System.err.println("ERROR: Verify.checkEQ failed: value mismatch. check raw: " + isFloatCheckWithRawBits);
Copy link
Member

Choose a reason for hiding this comment

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

Hm, it could indeed be a little bit more complicated when you are deep down in a recursion. My thought was that it could be misleading when a test is using a mix of verifyEQ() and verifyEQWithRawBits() and you only read verifyEQ failed. You could be start looking at the wrong check even though the stack trace would have guided you to the correct place. Maybe we can just update "Verify.checkEQ" into something more generic like "Equality matching failed" and we're good. What do you think?

if (isFloatEQ(a, b)) {
System.err.println("ERROR: Verify.checkEQ failed: value mismatch. check raw: " + isFloatCheckWithRawBits);
System.err.println(" Values: " + a + " vs " + b);
System.err.println(" Raw: " + Float.floatToRawIntBits(a) + " vs " + Float.floatToRawIntBits(b));
Copy link
Member

Choose a reason for hiding this comment

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

Sounds good, let's leave it in then!

Comment on lines 434 to 438
/**
* We do not want to import jdk.incubator.vector explicitly, because it would mean we would also have
* to add "--add-modules=jdk.incubator.vector" to the command-line of every test that uses the Verify
* class. So we hack this via reflection.
*/
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 background is only needed at checkEQForVectorAPIClass() (where you already have that comment). Here you can just describe what the code actually does or just drop the comment entirely since the method name is self-explanatory :-)


private void print(Object a, Object b, String field, Object aParent, Object bParent) {
System.err.println(" aParent: " + aParent);
System.err.println(" bParent: " + bParent);
Copy link
Member

Choose a reason for hiding this comment

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

Okay, maybe we can print <none> in case of a null for more clarity?

eme64 and others added 2 commits April 3, 2025 08:06
@eme64
Copy link
Contributor Author

eme64 commented Apr 3, 2025

@chhagedorn Thanks for having another look! I applied all your suggestions :)

Copy link
Member

@chhagedorn chhagedorn left a comment

Choose a reason for hiding this comment

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

That looks good to me, thanks for bearing with me!

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Apr 3, 2025
@eme64
Copy link
Contributor Author

eme64 commented Apr 4, 2025

@galderz do you intend to review / approve this, or should I ask someone else?

@eme64
Copy link
Contributor Author

eme64 commented Apr 7, 2025

@turbanoff Thanks for the two whitespace fixes :)

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Apr 7, 2025
@galderz
Copy link
Contributor

galderz commented Apr 8, 2025

@eme64 No, I don't think my review counts that much on this one. I think you need someone to review it that is has more background on the history of this.

@eme64
Copy link
Contributor Author

eme64 commented Apr 30, 2025

@galderz Christian already reviewed it, and generally it is ok for someone with deeper knowledge and someone with a little less familiarity to review. So far, on Verify.java the only other commit is #22715, so there is not too much history ;)

But totally up to you, I'm thankful for the comments you already left, and they do not obligate to do a complete review :)

Copy link
Member

@TobiHartmann TobiHartmann left a comment

Choose a reason for hiding this comment

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

Looks good to me! I just found a few minor typos.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label May 5, 2025
Co-authored-by: Tobias Hartmann <[email protected]>
@openjdk openjdk bot removed the ready Pull request is ready to be integrated label May 5, 2025
@eme64
Copy link
Contributor Author

eme64 commented May 6, 2025

@chhagedorn @TobiHartmann Thanks for the reviews :)

/integrate

@openjdk
Copy link

openjdk bot commented May 6, 2025

@eme64 This pull request has not yet been marked as ready for integration.

@eme64
Copy link
Contributor Author

eme64 commented May 6, 2025

@chhagedorn Thanks!

/integrate

@openjdk openjdk bot added the ready Pull request is ready to be integrated label May 6, 2025
@openjdk
Copy link

openjdk bot commented May 6, 2025

Going to push as commit 9f8fbf2.
Since your change was applied there have been 14 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

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

openjdk bot commented May 6, 2025

@eme64 Pushed as commit 9f8fbf2.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hotspot-compiler [email protected] integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

5 participants