Skip to content
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

[RISC-V][JIT] Fix NaN canonicalization issue #88510

Merged
merged 4 commits into from
Jul 21, 2023
Merged

Conversation

clamp03
Copy link
Member

@clamp03 clamp03 commented Jul 7, 2023

Based on the comments in #88311 (comment) and #88311 (comment), update codes to pass below tests.

./JIT/HardwareIntrinsics/General/Vector128/Vector128_r/Vector128_r.sh
./JIT/HardwareIntrinsics/General/Vector256/Vector256_r/Vector256_r.sh
./JIT/HardwareIntrinsics/General/Vector512/Vector512_r/Vector512_r.sh
./JIT/HardwareIntrinsics/General/Vector256/Vector256_ro/Vector256_ro.sh
./JIT/HardwareIntrinsics/General/Vector128/Vector128_ro/Vector128_ro.sh
./JIT/HardwareIntrinsics/General/Vector512/Vector512_ro/Vector512_ro.sh
./JIT/HardwareIntrinsics/General/Vector64/Vector64_r/Vector64_r.sh
./JIT/HardwareIntrinsics/General/Vector64/Vector64_ro/Vector64_ro.sh

I fixed all codes what I found. Minimum changes to pass the tests are those in emit.cpp, valuenum.cpp and NI_System_BitConverter_Int32BitsToSingle and NI_System_BitConverter_SingleToInt32Bits in importercalls.cpp.

Part of #84834
cc @wscho77 @HJLeee @JongHeonChoi @t-mustafin @alpencolt @gbalykov

@clamp03 clamp03 self-assigned this Jul 7, 2023
@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Jul 7, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 7, 2023
@ghost
Copy link

ghost commented Jul 7, 2023

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

Based on the comments in #88311 (comment) and #88311 (comment), update codes to pass below tests.

./JIT/HardwareIntrinsics/General/Vector128/Vector128_r/Vector128_r.sh
./JIT/HardwareIntrinsics/General/Vector256/Vector256_r/Vector256_r.sh
./JIT/HardwareIntrinsics/General/Vector512/Vector512_r/Vector512_r.sh
./JIT/HardwareIntrinsics/General/Vector256/Vector256_ro/Vector256_ro.sh
./JIT/HardwareIntrinsics/General/Vector128/Vector128_ro/Vector128_ro.sh
./JIT/HardwareIntrinsics/General/Vector512/Vector512_ro/Vector512_ro.sh
./JIT/HardwareIntrinsics/General/Vector64/Vector64_r/Vector64_r.sh
./JIT/HardwareIntrinsics/General/Vector64/Vector64_ro/Vector64_ro.sh

I fixed all codes what I found. Minimum changes to pass the tests are those in emit.cpp, valuenum.cpp and NI_System_BitConverter_Int32BitsToSingle and NI_System_BitConverter_SingleToInt32Bits in importercalls.cpp.

Part of #84834
cc @wscho77 @HJLeee @JongHeonChoi @t-mustafin @alpencolt @gbalykov

Author: clamp03
Assignees: clamp03
Labels:

area-CodeGen-coreclr, community-contribution

Milestone: -

@clamp03 clamp03 added the arch-riscv Related to the RISC-V architecture label Jul 7, 2023
Copy link
Member

@jakobbotsch jakobbotsch left a comment

Choose a reason for hiding this comment

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

This logic should be extracted into a new helper in FloatingPointUtils, e.g. FloatingPointUtils::ConvertToDouble that preserves the payload bits and is a noop for everything but RISCV64. We do not want to add these #ifdef TARGET_RISCV everywhere.

goto DONE_ASSERTION;
}
}
#endif // TARGET_RISCV64
Copy link
Member

Choose a reason for hiding this comment

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

Why is this change necessary? _isnan should not care about payload bits.

Copy link
Member Author

@clamp03 clamp03 Jul 7, 2023

Choose a reason for hiding this comment

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

Sure. I will make a new helper. Thank you. I wanted to know proper location and name. :)

Because when a float value is converted to Double with preserving payload and saved in DconValue, the saved Double value is not 'NaN' in Double type.
For example, c can be assumed as f64Cns in the change.

    int a = -1;
    double c = *reinterpret_cast<double*>(&a);

    float d = *reinterpret_cast<float*>(&c);
    float e = (float)c;
    fprintf(stderr, "%lf %f %f %d %d %d\n", c, d, e, isnan(c), isnan(d), isnan(e));

0.000000 nan 0.000000 0 1 0
Only the *reinterpret_cast<float*>(&c) is NaN in the example.
If I misunderstand your comment, please let me know.
Thank you!

Copy link
Member

Choose a reason for hiding this comment

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

Conversion to a double by reinterpreting a 32 bit int is not correct. The right way to do it is something that constructs the uint64_t bits directly by ORing in the desired payload bits in the right place and returns UInt64BitsToDouble from that. It should mimic what our other hosts do for float -> double conversions. We should not be trying to store a 32 bit float in the 64 bit double field.

I currently do not have access to a PC, but @tannergooding may be able to help with the exact right way to do the conversion that retains the payload bits on the proper way.

Copy link
Member Author

@clamp03 clamp03 Jul 7, 2023

Choose a reason for hiding this comment

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

The PR updates codes to use fsd/flw (preserving payload) or fcvt (not preserving payload) in case of float is NaN or not.
I will wait for the exact solution. Thank you so much!!!

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 there are a few possible options. For the JIT side, all of them have the issue that the general codebase would need to be "audited" to ensure that all float->double handling is done correctly. We're not consistent about this today.

Update GenTreeDblCon to be more like GenTreeIcon

You can update GenTreeDblCon to have a union of gtDconVal and gtFconVal. It would then expose float FconValue() and void SetFconValue(float value) and assert that Dcon is only called for TYP_DOUBLE and Fcon is only called for `TYP_FLOAT

This will ensure that the values aren't ever accessed incorrectly and avoids the upcast/downcast issue entirely for the JIT. It does not resolve the issue for managed code and will likely have some minor TP hit due to needing to add branching to various callsites in the JIT.

Expose a payload preserving conversion helper

Just as it says, define a helper API that does a payload preserving conversion. This would be a software based conversion and would simply ensure the significand bits are copied up and truncated down, just as happens for other platforms.

This helper would be used by both the JIT and managed and ensures managed code also gets the right handling. This would incur a branch check per conversion on RISC-V.

Explicitly utilize FMV.n.X/FMV.X.n

These instructions allow storing n-bits in the larger f register, preserving the payload. The value stored is a NaN boxed value.

One could utilize this to more efficiently store the data in gtDconVal, but it has many of the same issues/considerations where it needs to be done everywhere and consistently; otherwise subtle bugs could creep in.

Copy link
Member

Choose a reason for hiding this comment

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

I'd suggest to introduce the payload preserving conversion helper for now and use it in the cases found in this PR. I think that's going to be the least invasive fix. We can consider the more complete/invasive fixes as part of .NET 9.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jakobbotsch @tannergooding Thank you so much for the comments.
I made a conversion helper and updated all. However, I don't know what is the right way to convert, so I just retained the conversion method as it is. Could you please give the right way what you mentioned?

Conversion to a double by reinterpreting a 32 bit int is not correct. The right way to do it is something that constructs the uint64_t bits directly by ORing in the desired payload bits in the right place and returns UInt64BitsToDouble from that.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jakobbotsch @tannergooding Thank you so much for the comments. I made a conversion helper and updated all. However, I don't know what is the right way to convert, so I just retained the conversion method as it is. Could you please give the right way what you mentioned?

Conversion to a double by reinterpreting a 32 bit int is not correct. The right way to do it is something that constructs the uint64_t bits directly by ORing in the desired payload bits in the right place and returns UInt64BitsToDouble from that.

Could you take a look? Thank you so much.

Copy link
Member

Choose a reason for hiding this comment

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

@clamp03 The conversion functions should look something like:

//------------------------------------------------------------------------
// convertToDouble: Convert a single to a double with platform independent
// preservation of payload bits.
//
// Arguments:
//   f - the single
//
// Return Value:
//   A double.
//
// Remarks:
//   All our host platforms except for RISCV-64 will preserve payload bits of
//   NaNs. This function implements the conversion in software for RISCV-64 to
//   mimic other platforms.
//
double FloatingPointUtils::convertToDouble(float f)
{
#ifdef HOST_RISCV64
    if (f == f)
    {
        return f;
    }

    uint32_t bits = BitOperations::SingleToUInt32Bits(f);
    uint32_t payload = bits & ((1u << 23) - 1);
    uint64_t newBits = ((uint64_t)(bits >> 31) << 63) | 0x7FF8'0000'0000'0000ul | ((uint64_t)payload << 29);
    return BitOperations::UInt64BitsToDouble(newBits);
#else
    return f;
#endif
}

//------------------------------------------------------------------------
// convertToSingle: Convert a double to a single with platform independent
// preservation of payload bits.
//
// Arguments:
//   d - the double
//
// Return Value:
//   A float.
//
// Remarks:
//   All our host platforms except for RISCV-64 will preserve payload bits of
//   NaNs. This function implements the conversion in software for RISCV-64 to
//   mimic other platforms.
//
float FloatingPointUtils::convertToSingle(double d)
{
#ifdef HOST_RISCV64
    if (d == d)
    {
        return (float)d;
    }

    uint64_t bits = BitOperations::DoubleToUInt64Bits(d);
    uint32_t newPayload = (uint32_t)((bits >> 29) & ((1u << 23) - 1));
    uint32_t newBits = ((uint32_t)(bits >> 63) << 31) | 0x7F80'0000u | newPayload;
    return BitOperations::UInt32BitsToSingle(newBits);
#else
    return (float)d;
#endif
}

You should change the JIT to use this conversion function in the places you have identified.

Copy link
Member Author

Choose a reason for hiding this comment

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

Awesome!!! I didn't think so. Thank you so much.

@ghost ghost added needs-author-action An issue or pull request that requires more info or actions from the author. and removed needs-author-action An issue or pull request that requires more info or actions from the author. labels Jul 7, 2023
@@ -3794,7 +3794,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
if (op1->IsIntegralConst())
{
int32_t i32Cns = (int32_t)op1->AsIntConCommon()->IconValue();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
int32_t i32Cns = (int32_t)op1->AsIntConCommon()->IconValue();

@@ -3794,7 +3794,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
if (op1->IsIntegralConst())
{
int32_t i32Cns = (int32_t)op1->AsIntConCommon()->IconValue();
retNode = gtNewDconNode(*reinterpret_cast<float*>(&i32Cns), TYP_FLOAT);
float f32Cns = *reinterpret_cast<float*>(&i32Cns);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
float f32Cns = *reinterpret_cast<float*>(&i32Cns);
float f32Cns = BitOperations::UInt32BitsToSingle((uint32_t)op1->AsIntConCommon()->IconValue());

Now that you're here...

@@ -3834,7 +3835,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,

if (op1->IsCnsFltOrDbl())
{
float f32Cns = (float)op1->AsDblCon()->DconValue();
float f32Cns = FloatingPointUtils::convertToSingle(op1->AsDblCon()->DconValue());
retNode = gtNewIconNode(*reinterpret_cast<int32_t*>(&f32Cns));
Copy link
Member

@jakobbotsch jakobbotsch Jul 20, 2023

Choose a reason for hiding this comment

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

Suggested change
retNode = gtNewIconNode(*reinterpret_cast<int32_t*>(&f32Cns));
retNode = gtNewIconNode((int32_t)BitOperations::SingleToUInt32Bits(f32Cns));

(since we're here anyways)

Comment on lines 2700 to 2702
conValTree =
gtNewDconNode(FloatingPointUtils::convertToDouble(*reinterpret_cast<float*>(&value)),
TYP_FLOAT);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
conValTree =
gtNewDconNode(FloatingPointUtils::convertToDouble(*reinterpret_cast<float*>(&value)),
TYP_FLOAT);
conValTree =
gtNewDconNode(FloatingPointUtils::convertToDouble(BitOperations::UInt32BitsToSingle((uint32_t)value)),
TYP_FLOAT);

@clamp03
Copy link
Member Author

clamp03 commented Jul 20, 2023

Thank you so much. I will think and try more for the better codes.

Copy link
Member

@jakobbotsch jakobbotsch left a comment

Choose a reason for hiding this comment

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

Thanks!

@jakobbotsch jakobbotsch merged commit 84bffc8 into dotnet:main Jul 21, 2023
@clamp03 clamp03 deleted the fixNaN branch July 24, 2023 02:39
@ghost ghost locked as resolved and limited conversation to collaborators Aug 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
arch-riscv Related to the RISC-V architecture area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants