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

ILDasm regression in relation to double (float64) and float (float32) values #111014

Open
pcf0 opened this issue Dec 31, 2024 · 6 comments
Open
Labels
area-ILTools-coreclr regression-from-last-release untriaged New issue has not been triaged by the area owner

Comments

@pcf0
Copy link

pcf0 commented Dec 31, 2024

Description

ILDasm (9.0) outputs the wrong format in float64(...) and float32(...) for double and float values without decimal places. The decimal point is missing.
ILAsm interprets these as int64 and therefore casts them to double or float values, which leads to incorrect values.

Reproduction Steps

  1. Compile the code to DLL
  2. ildasm Program.dll /out=Program.il
  3. ilasm Program.il /DLL
  4. ildasm Program.dll /out=Program.il (to see the effect)
public static class Program
{
    public const double ValueDoubleOne = 1.0d;
    public const double ValueDoubleOnePointTwo = 1.2d;
    public const double ValueDoubleMinusOne = -1.0d;
    public const double ValueDoubleMinusZero = -0.0d;
    public const float ValueFloatOne = 1.0f;
    public const float ValueFloatOnePointTwo = 1.2f;
    public const float ValueFloatMinusOne = -1.0f;
    public const float ValueFloatMinusZero = -0.0f;
}

or

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(1.)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(-1.)
  .field public static literal float64 ValueDoubleMinusZero = float64(-0.)
  .field public static literal float32 ValueFloatOne = float32(1.)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(-1.)
  .field public static literal float32 ValueFloatMinusZero = float32(-0.)
}

Expected behavior

Output should be the same as input:

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(1.)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(-1.)
  .field public static literal float64 ValueDoubleMinusZero = float64(-0.)
  .field public static literal float32 ValueFloatOne = float32(1.)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(-1.)
  .field public static literal float32 ValueFloatMinusZero = float32(-0.)
}

Actual behavior

The IL code from step 2 will be (note the missing .s):

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(1)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(-1)
  .field public static literal float64 ValueDoubleMinusZero = float64(-0)
  .field public static literal float32 ValueFloatOne = float32(1)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(-1)
  .field public static literal float32 ValueFloatMinusZero = float32(-0)
}

After a roundtrip in step 4 the IL code will be:

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(4.9406564584124654e-324)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(0xFFFFFFFFFFFFFFFF) // -nan
  .field public static literal float64 ValueDoubleMinusZero = float64(0)
  .field public static literal float32 ValueFloatOne = float32(1.4012985e-45)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(0xFFFFFFFF)
  .field public static literal float32 ValueFloatMinusZero = float32(0)
}

Regression?

Yes, it is a regression, the following versions of ILDasm work:

  • 4.0.30319.0
  • 8.0.0
  • 9.0.0-preview.3.24172.9

The first version that no longer works is: 9.0.0-preview.4.24266.19
I think the behavior was changed with this PR: #98336

Known Workarounds

As a workaround, you can only correct the output using appropriate regex.

[GeneratedRegex(@"float(32|64)\((-?\d+)\)")]
private static partial Regex FloatNumber { get; }

if (FloatNumber.IsMatch(ilcode))
{
    ilcode = FloatNumber.Replace(ilcode, "float$1($2.)");
}

Configuration

  • NET 9.0
  • Windows 10 22H2 x64
  • It should be independent of the configuration, as the code is used for all configurations. However, I have not tested it.

Other information

I am not an expert in C++, but I think the problem should be fixed by adding this code snippet after the two places?

sprintf_s(szf, 32, "%.*g", 8, (double)MDDV.m_fltValue);

sprintf_s(szf, 32, "%.*g", 17, MDDV.m_dblValue);

if (strchr(szf, '.') == NULL && !IsSpecialNumber(szf))
    strcat_s(szf, 32, ".");

Although these places were also affected by the change, they do not seem to make any difference for ILAsm:

sprintf_s(str, 64, "%.*g", 8, (double)(*((float*)dataPtr)));

sprintf_s(str, 64, "%.*g", 17, *((double*)dataPtr));

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Dec 31, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

@jkotas
Copy link
Member

jkotas commented Dec 31, 2024

I think the behavior was changed with this PR: #98336

cc @jkoritzinsky

Copy link
Contributor

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

@tannergooding
Copy link
Member

tannergooding commented Dec 31, 2024

ILAsm interprets these as int64 and therefore casts them to double or float values, which leads to incorrect values.

Notably this isn't a cast, but rather is interpreting it as the raw bitwise representation, that is = float64(1) is effectively doing = BitConverter.Int64BitsToDouble(1) and therefore producing double.Epsilon as the result, it is not doing = (double)1UL (which would produce 1.0 as expected).

@hez2010
Copy link
Contributor

hez2010 commented Dec 31, 2024

Yes, it is a regression, the following versions of ILDasm work:

  • 4.0.30319.0
  • 8.0.0
  • 9.0.0-preview.3.24172.9

https://godbolt.org/z/exzocbrzM

It seems that it doesn't work on 8.0.0?

@pcf0
Copy link
Author

pcf0 commented Dec 31, 2024

Notably this isn't a cast, but rather is interpreting it as the raw bitwise representation, [...]

Maybe I used the wrong wording. As I said, I'm not a C++ expert.
However, I meant it exactly as you describe it. From this code:

| FLOAT32_ '(' int32 ')' { float f; *((int32_t*) (&f)) = $3; $$ = new double(f); }
| FLOAT64_ '(' int64 ')' { $$ = (double*) $3; }

It seems that it doesn't work on 8.0.0?

Then there must be something wrong with gobolt, here is the IL code I just generated with the 8.0.0 version. (https://www.nuget.org/packages/runtime.win-x64.Microsoft.NETCore.ILDAsm/8.0.0)

//  .NET IL Disassembler.  Version 8.0.0



// Metadata version: v4.0.30319
.assembly extern System.Runtime
{
  .ver 0:0:0:0
}
.module Program.dll
// MVID: {21758ade-0f42-4b59-8c4a-af87a71559ae}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x000001E44FB80000


// =============== CLASS MEMBERS DECLARATION ===================

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(1.)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(-1.)
  .field public static literal float64 ValueDoubleMinusZero = float64(-0.)
  .field public static literal float32 ValueFloatOne = float32(1.)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(-1.)
  .field public static literal float32 ValueFloatMinusZero = float32(-0.)
} // end of class Program


// =============================================================

// *********** DISASSEMBLY COMPLETE ***********************

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-ILTools-coreclr regression-from-last-release untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

4 participants