Browse Source

Fix #1794: Avoid extra cast to double for ulong -> float conversion

pull/1820/head
Daniel Grunwald 6 years ago
parent
commit
a1c211f326
  1. 10
      ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
  2. 60
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/FloatingPointArithmetic.cs
  3. 2
      ICSharpCode.Decompiler/IL/ILReader.cs
  4. 21
      ICSharpCode.Decompiler/IL/ILTypeExtensions.cs
  5. 5
      ICSharpCode.Decompiler/IL/Instructions/Conv.cs
  6. 11
      ICSharpCode.Decompiler/IL/PrimitiveType.cs
  7. 11
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  8. 1
      ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

10
ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs

@ -83,8 +83,14 @@ namespace ICSharpCode.Decompiler.Tests @@ -83,8 +83,14 @@ namespace ICSharpCode.Decompiler.Tests
}
[Test]
public void FloatingPointArithmetic([ValueSource("defaultOptions")] CompilerOptions options)
{
public void FloatingPointArithmetic([ValueSource("noMonoOptions")] CompilerOptions options, [Values(32, 64)] int bits)
{
// The behavior of the #1794 incorrect `(float)(double)val` cast only causes test failures
// for some runtime+compiler combinations.
if (bits == 32)
options |= CompilerOptions.Force32Bit;
// Mono is excluded because we never use it for the second pass, so the test ends up failing
// due to some Mono vs. Roslyn compiler differences.
RunCS(options: options);
}

60
ICSharpCode.Decompiler.Tests/TestCases/Correctness/FloatingPointArithmetic.cs

@ -8,12 +8,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -8,12 +8,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{
Issue999();
Issue1656();
Issue1794();
return 0;
}
static void Issue999()
{
for (float i = -10f; i <= 10f; i += 0.01f)
for (float i = -10f; i <= 10f; i += 0.01f)
Console.WriteLine("{1:R}: {0:R}", M(i), i);
}
@ -31,6 +32,63 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -31,6 +32,63 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
CxAssert((primary--) == 'C');
}
static void Issue1794()
{
Console.WriteLine("CastUnsignedToFloat:");
Console.WriteLine(CastUnsignedToFloat(9007199791611905).ToString("r"));
Console.WriteLine("CastUnsignedToDouble:");
Console.WriteLine(CastUnsignedToDouble(9007199791611905).ToString("r"));
Console.WriteLine("CastUnsignedToFloatViaDouble:");
Console.WriteLine(CastUnsignedToFloatViaDouble(9007199791611905).ToString("r"));
Console.WriteLine("CastSignedToFloat:");
Console.WriteLine(CastSignedToFloat(9007199791611905).ToString("r"));
Console.WriteLine("ImplicitCastSignedToFloat:");
Console.WriteLine(ImplicitCastSignedToFloat(9007199791611905).ToString("r"));
Console.WriteLine("CastSignedToDouble:");
Console.WriteLine(CastSignedToDouble(9007199791611905).ToString("r"));
Console.WriteLine("CastSignedToFloatViaDouble:");
Console.WriteLine(CastSignedToFloatViaDouble(9007199791611905).ToString("r"));
}
static float CastUnsignedToFloat(ulong val)
{
return (float)val;
}
static double CastUnsignedToDouble(ulong val)
{
return (double)val;
}
static float CastUnsignedToFloatViaDouble(ulong val)
{
// The double-rounding can increase the rounding error
return (float)(double)val;
}
static float CastSignedToFloat(long val)
{
return (float)val;
}
static double CastSignedToDouble(long val)
{
return (double)val;
}
static float CastSignedToFloatViaDouble(long val)
{
// The double-rounding can increase the rounding error
return (float)(double)val;
}
static float ImplicitCastSignedToFloat(long val)
{
return val;
}
static void CxAssert(bool v)
{
if (!v) {

2
ICSharpCode.Decompiler/IL/ILReader.cs

@ -666,7 +666,7 @@ namespace ICSharpCode.Decompiler.IL @@ -666,7 +666,7 @@ namespace ICSharpCode.Decompiler.IL
case ILOpCode.Conv_u:
return Push(new Conv(Pop(), PrimitiveType.U, false, Sign.None));
case ILOpCode.Conv_r_un:
return Push(new Conv(Pop(), PrimitiveType.R8, false, Sign.Unsigned));
return Push(new Conv(Pop(), PrimitiveType.R, false, Sign.Unsigned));
case ILOpCode.Conv_ovf_i1:
return Push(new Conv(Pop(), PrimitiveType.I1, true, Sign.Signed));
case ILOpCode.Conv_ovf_i2:

21
ICSharpCode.Decompiler/IL/ILTypeExtensions.cs

@ -38,17 +38,14 @@ namespace ICSharpCode.Decompiler.IL @@ -38,17 +38,14 @@ namespace ICSharpCode.Decompiler.IL
return StackType.I8;
case PrimitiveType.I:
case PrimitiveType.U:
case (PrimitiveType)0x0f: // Ptr
case (PrimitiveType)0x1b: // FnPtr
return StackType.I;
case PrimitiveType.R4:
return StackType.F4;
case PrimitiveType.R8:
case PrimitiveType.R:
return StackType.F8;
case (PrimitiveType)0x10: // ByRef
case PrimitiveType.Ref: // ByRef
return StackType.Ref;
case (PrimitiveType)0x01: // Void
return StackType.Void;
case PrimitiveType.Unknown:
return StackType.Unknown;
default:
@ -65,6 +62,7 @@ namespace ICSharpCode.Decompiler.IL @@ -65,6 +62,7 @@ namespace ICSharpCode.Decompiler.IL
case PrimitiveType.I8:
case PrimitiveType.R4:
case PrimitiveType.R8:
case PrimitiveType.R:
case PrimitiveType.I:
return Sign.Signed;
case PrimitiveType.U1:
@ -100,6 +98,7 @@ namespace ICSharpCode.Decompiler.IL @@ -100,6 +98,7 @@ namespace ICSharpCode.Decompiler.IL
case PrimitiveType.I8:
case PrimitiveType.R8:
case PrimitiveType.U8:
case PrimitiveType.R:
return 8;
case PrimitiveType.I:
case PrimitiveType.U:
@ -126,6 +125,18 @@ namespace ICSharpCode.Decompiler.IL @@ -126,6 +125,18 @@ namespace ICSharpCode.Decompiler.IL
return primitiveType.GetStackType().IsIntegerType();
}
public static bool IsFloatType(this PrimitiveType type)
{
switch (type) {
case PrimitiveType.R4:
case PrimitiveType.R8:
case PrimitiveType.R:
return true;
default:
return false;
}
}
/// <summary>
/// Infers the C# type for an IL instruction.
///

5
ICSharpCode.Decompiler/IL/Instructions/Conv.cs

@ -48,7 +48,7 @@ namespace ICSharpCode.Decompiler.IL @@ -48,7 +48,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Converts from the current precision available on the evaluation stack to the precision specified by
/// the <c>TargetType</c>.
/// Uses "round-to-nearest" mode is the precision is reduced.
/// Uses "round-to-nearest" mode if the precision is reduced.
/// </summary>
FloatPrecisionChange,
/// <summary>
@ -156,7 +156,7 @@ namespace ICSharpCode.Decompiler.IL @@ -156,7 +156,7 @@ namespace ICSharpCode.Decompiler.IL
public Conv(ILInstruction argument, StackType inputType, Sign inputSign, PrimitiveType targetType, bool checkForOverflow, bool isLifted = false)
: base(OpCode.Conv, argument)
{
bool needsSign = checkForOverflow || (!inputType.IsFloatType() && (targetType == PrimitiveType.R4 || targetType == PrimitiveType.R8));
bool needsSign = checkForOverflow || (!inputType.IsFloatType() && targetType.IsFloatType());
Debug.Assert(!(needsSign && inputSign == Sign.None));
this.InputSign = needsSign ? inputSign : Sign.None;
this.InputType = inputType;
@ -264,6 +264,7 @@ namespace ICSharpCode.Decompiler.IL @@ -264,6 +264,7 @@ namespace ICSharpCode.Decompiler.IL
default:
return ConversionKind.Invalid;
}
case PrimitiveType.R:
case PrimitiveType.R8:
switch (inputType) {
case StackType.I4:

11
ICSharpCode.Decompiler/IL/PrimitiveType.cs

@ -35,7 +35,18 @@ namespace ICSharpCode.Decompiler.IL @@ -35,7 +35,18 @@ namespace ICSharpCode.Decompiler.IL
U8 = PrimitiveTypeCode.UInt64,
I = PrimitiveTypeCode.IntPtr,
U = PrimitiveTypeCode.UIntPtr,
/// <summary>Managed reference</summary>
Ref = 16,
/// <summary>Floating point type of unspecified size:
/// usually 80 bits on x86 (when the runtime uses x87 instructions);
/// but only 64-bit on x64.
/// This only occurs for "conv.r.un" instructions. The C# compiler usually follows those
/// with a "conv.r4" or "conv.r8" instruction to indicate the desired float type, so
/// we only use this as conversion target type and don't bother tracking it as its own stack type:
/// basically everything treats R identical to R8, except for the (conv.r.un + conv.r[48] => conv.r[48].un)
/// combining logic which should not combine (conv.r.un + conv.r8 + conv.r4) into a single conv.r4.un.
/// </summary>
R = 254,
Unknown = 255
}
}

11
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -177,6 +177,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -177,6 +177,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.Step("conv.i4(ldlen array) => ldlen.i4(array)", inst);
inst.AddILRange(inst.Argument);
inst.ReplaceWith(new LdLen(inst.TargetType.GetStackType(), array).WithILRange(inst));
return;
}
if (inst.TargetType.IsFloatType() && inst.Argument is Conv conv
&& conv.Kind == ConversionKind.IntToFloat && conv.TargetType == PrimitiveType.R)
{
// IL conv.r.un does not indicate whether to convert the target type to R4 or R8,
// so the C# compiler usually follows it with an explicit conv.r4 or conv.r8.
// To avoid emitting '(float)(double)val', we combine these two conversions:
context.Step("conv.rN(conv.r.un(...)) => conv.rN.un(...)", inst);
inst.ReplaceWith(new Conv(conv.Argument, conv.InputType, conv.InputSign, inst.TargetType, inst.CheckForOverflow, inst.IsLifted | conv.IsLifted));
return;
}
}

1
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -398,6 +398,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -398,6 +398,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
case PrimitiveType.R4:
return KnownTypeCode.Single;
case PrimitiveType.R8:
case PrimitiveType.R:
return KnownTypeCode.Double;
case PrimitiveType.U1:
return KnownTypeCode.Byte;

Loading…
Cancel
Save