AstroRP
AstroRP

Reputation: 267

How is a decompiler able to recognize a compiled constant?

I am using ILSpy to decompile the assemblies of .Net and look into the code. As I was browsing the code of System.Windows.Vector.AngleBetween(Vector, Vector) in WindowsBase.dll, I stumbled across something bizarre.

This is the full code of the function:

public static double AngleBetween(Vector vector1, Vector vector2)
{
    double y = vector1._x * vector2._y - vector2._x * vector1._y;
    double x = vector1._x * vector2._x + vector1._y * vector2._y;
    return Math.Atan2(y, x) * (180.0 / Math.PI);
}

Apparently ILSpy could recognize Math.PI, which is a constant.

This is what Microsoft Docs says about constants in C#:

In fact, when the compiler encounters a constant identifier in C# source code, it substitutes the literal value directly into the intermediate language (IL) code that it produces.

Based on this, what ILSpy did seems impossible.

Note: this behavior is present even if the "Use variable names from debug symbols, if available" and "Show info from debug symbols, if available" options are unchecked in the settings.

Upvotes: 3

Views: 809

Answers (2)

Bradley Uffner
Bradley Uffner

Reputation: 17001

A quick test in LINQPad shows some differences in the IL code generated (PI constant value copied from here).

Source:

void Main()
{
    double a = Math.PI;
    double b = 3.14159265358979;
}

IL:

IL_0000:  nop         
IL_0001:  ldc.r8      18 2D 44 54 FB 21 09 40 
IL_000A:  stloc.0     // a
IL_000B:  ldc.r8      11 2D 44 54 FB 21 09 40 
IL_0014:  stloc.1     // b
IL_0015:  ret   

There does seem to be a subtle difference in the IL code generated between the constant and the literal values, but I'm not sure exactly what it means yet.


The above values from the MSDN documentation seem to contradict information from the .NET Reference Source (See comments). With adjusted code from the source, the IL is identical.

Source:

void Main()
{
    var a = Math.PI;
    var b = 3.14159265358979323846;
}

IL:

IL_0000:  nop         
IL_0001:  ldc.r8      18 2D 44 54 FB 21 09 40 
IL_000A:  stloc.0     // a
IL_000B:  ldc.r8      18 2D 44 54 FB 21 09 40 
IL_0014:  stloc.1     // b
IL_0015:  ret   

As an aside, it looks like there is an open issue to address the documentation / source code inconsistency.

Upvotes: 0

Ghost4Man
Ghost4Man

Reputation: 1072

As you can see in this ILSpy issue and the corresponding pull request, this was specifically implemented (hardcoded) for well-known values such as Math.PI.

From the GitHub issue:

I suppose to calculate pi coefficient by following way: c = Math.PI / constant. If we getting "good" value (equal exactly to 1.0, 2.0, 0.5, 1/180 and so on), we simply replacing it with symbolic expression (Math.PI, Math.PI * 2, Math.PI / 2, Math.PI / 180 and so on).

Upvotes: 6

Related Questions