Browse Source

[nullables] Add support for lifted binary operators where one of the inputs is nullable.

pull/863/head
Daniel Grunwald 8 years ago
parent
commit
e266c634de
  1. 2
      BuildTools/tidy.py
  2. 7
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
  3. 66
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 22
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  5. 48
      ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs
  6. 2
      ICSharpCode.Decompiler/IL/Instructions/Conv.cs
  7. 40
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

2
BuildTools/tidy.py

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
#!/usr/bin/env python
import os, sys
def check(filename):

7
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs

@ -802,7 +802,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -802,7 +802,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public long? InArithmetic(uint? b)
{
return long.MinValue + b;
return 100L + b;
}
public long? AfterArithmetic(uint? b)
{
return 100 + b;
}
static double? InArithmetic2(float? nf, double? nd, float f)

66
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -216,8 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -216,8 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp
var dimensions = inst.Indices.Count;
var args = inst.Indices.Select(arg => TranslateArrayIndex(arg)).ToArray();
var expr = new ArrayCreateExpression { Type = ConvertType(inst.Type) };
var ct = expr.Type as ComposedType;
if (ct != null) {
if (expr.Type is ComposedType ct) {
// change "new (int[,])[10] to new int[10][,]"
ct.ArraySpecifiers.MoveTo(expr.AdditionalArraySpecifiers);
}
@ -599,18 +598,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -599,18 +598,18 @@ namespace ICSharpCode.Decompiler.CSharp
var resolverWithOverflowCheck = resolver.WithCheckForOverflow(inst.CheckForOverflow);
var left = Translate(inst.Left);
var right = Translate(inst.Right);
left = PrepareArithmeticArgument(left, inst.Left.ResultType, inst.Sign);
right = PrepareArithmeticArgument(right, inst.Right.ResultType, inst.Sign);
left = PrepareArithmeticArgument(left, inst.LeftInputType, inst.Sign, inst.IsLifted);
right = PrepareArithmeticArgument(right, inst.RightInputType, inst.Sign, inst.IsLifted);
var rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult);
if (rr.IsError || rr.Type.GetStackType() != inst.ResultType
if (rr.IsError || NullableType.GetUnderlyingType(rr.Type).GetStackType() != inst.UnderlyingResultType
|| !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign))
{
// Left and right operands are incompatible, so convert them to a common type
StackType targetStackType = inst.ResultType == StackType.I ? StackType.I8 : inst.ResultType;
StackType targetStackType = inst.UnderlyingResultType == StackType.I ? StackType.I8 : inst.UnderlyingResultType;
IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(inst.Sign));
left = left.ConvertTo(targetType, this);
right = right.ConvertTo(targetType, this);
left = left.ConvertTo(NullableType.IsNullable(left.Type) ? NullableType.Create(compilation, targetType) : targetType, this);
right = right.ConvertTo(NullableType.IsNullable(right.Type) ? NullableType.Create(compilation, targetType) : targetType, this);
rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult);
}
var resultExpr = new BinaryOperatorExpression(left.Expression, op, right.Expression)
@ -624,18 +623,28 @@ namespace ICSharpCode.Decompiler.CSharp @@ -624,18 +623,28 @@ namespace ICSharpCode.Decompiler.CSharp
/// <summary>
/// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments.
/// </summary>
TranslatedExpression PrepareArithmeticArgument(TranslatedExpression arg, StackType argStackType, Sign sign)
TranslatedExpression PrepareArithmeticArgument(TranslatedExpression arg, StackType argStackType, Sign sign, bool isLifted)
{
if (argStackType.IsIntegerType() && argStackType.GetSize() < arg.Type.GetSize()) {
if (isLifted && !NullableType.IsNullable(arg.Type)) {
isLifted = false; // don't cast to nullable if this input wasn't already nullable
}
IType argUType = isLifted ? NullableType.GetUnderlyingType(arg.Type) : arg.Type;
if (argStackType.IsIntegerType() && argStackType.GetSize() < argUType.GetSize()) {
// If the argument is oversized (needs truncation to match stack size of its ILInstruction),
// perform the truncation now.
arg = arg.ConvertTo(compilation.FindType(argStackType.ToKnownTypeCode(sign)), this);
IType targetType = compilation.FindType(argStackType.ToKnownTypeCode(sign));
if (isLifted)
targetType = NullableType.Create(compilation, targetType);
arg = arg.ConvertTo(targetType, this);
}
if (arg.Type.GetStackType() == StackType.I) {
if (argUType.GetStackType() == StackType.I) {
// None of the operators we might want to apply are supported by IntPtr/UIntPtr.
// Also, pointer arithmetic has different semantics (works in number of elements, not bytes).
// So any inputs of size StackType.I must be converted to long/ulong.
arg = arg.ConvertTo(compilation.FindType(StackType.I8.ToKnownTypeCode(sign)), this);
IType targetType = compilation.FindType(StackType.I8.ToKnownTypeCode(sign));
if (isLifted)
targetType = NullableType.Create(compilation, targetType);
arg = arg.ConvertTo(targetType, this);
}
return arg;
}
@ -646,7 +655,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -646,7 +655,7 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary>
static bool IsCompatibleWithSign(IType type, Sign sign)
{
return sign == Sign.None || type.GetSign() == sign;
return sign == Sign.None || NullableType.GetUnderlyingType(type).GetSign() == sign;
}
static bool BinaryOperatorMightCheckForOverflow(BinaryOperatorType op)
@ -669,32 +678,45 @@ namespace ICSharpCode.Decompiler.CSharp @@ -669,32 +678,45 @@ namespace ICSharpCode.Decompiler.CSharp
var right = Translate(inst.Right);
Sign sign = inst.Sign;
if (left.Type.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.ResultType == StackType.I4) {
var leftUType = NullableType.GetUnderlyingType(left.Type);
if (leftUType.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.UnderlyingResultType == StackType.I4) {
// With small integer types, C# will promote to int and perform signed shifts.
// We thus don't need any casts in this case.
} else {
// Insert cast to target type.
if (sign == Sign.None) {
// if we don't need a specific sign, prefer keeping that of the input:
sign = left.Type.GetSign();
sign = leftUType.GetSign();
}
IType targetType;
if (inst.ResultType == StackType.I4)
if (inst.UnderlyingResultType == StackType.I4) {
targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32);
else
} else {
targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64);
}
if (NullableType.IsNullable(left.Type)) {
targetType = NullableType.Create(compilation, targetType);
}
left = left.ConvertTo(targetType, this);
}
// Shift operators in C# always expect type 'int' on the right-hand-side
right = right.ConvertTo(compilation.FindType(KnownTypeCode.Int32), this);
if (NullableType.IsNullable(right.Type)) {
right = right.ConvertTo(NullableType.Create(compilation, compilation.FindType(KnownTypeCode.Int32)), this);
} else {
right = right.ConvertTo(compilation.FindType(KnownTypeCode.Int32), this);
}
TranslatedExpression result = new BinaryOperatorExpression(left.Expression, op, right.Expression)
.WithILInstruction(inst)
.WithRR(resolver.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult));
if (inst.ResultType == StackType.I) {
if (inst.UnderlyingResultType == StackType.I) {
// C# doesn't have shift operators for IntPtr, so we first shifted a long/ulong,
// and now have to case back down to IntPtr/UIntPtr:
result = result.ConvertTo(compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UIntPtr : KnownTypeCode.IntPtr), this);
IType targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UIntPtr : KnownTypeCode.IntPtr);
if (inst.IsLifted)
targetType = NullableType.Create(compilation, targetType);
result = result.ConvertTo(targetType, this);
}
return result;
}
@ -731,7 +753,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -731,7 +753,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
var target = Translate(inst.Target);
var value = Translate(inst.Value);
value = PrepareArithmeticArgument(value, inst.Value.ResultType, inst.Sign);
value = PrepareArithmeticArgument(value, inst.Value.ResultType, inst.Sign, isLifted: false);
TranslatedExpression resultExpr;
if (inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToOldValue) {

22
ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

@ -1,4 +1,22 @@ @@ -1,4 +1,22 @@
using ICSharpCode.Decompiler.CSharp;
// Copyright (c) 2017 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
@ -6,8 +24,6 @@ using System; @@ -6,8 +24,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{

48
ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs

@ -37,7 +37,7 @@ namespace ICSharpCode.Decompiler.IL @@ -37,7 +37,7 @@ namespace ICSharpCode.Decompiler.IL
ShiftRight
}
public partial class BinaryNumericInstruction : BinaryInstruction
public partial class BinaryNumericInstruction : BinaryInstruction, ILiftableInstruction
{
/// <summary>
/// Gets whether the instruction checks for overflow.
@ -51,11 +51,29 @@ namespace ICSharpCode.Decompiler.IL @@ -51,11 +51,29 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
public readonly Sign Sign;
public readonly StackType LeftInputType;
public readonly StackType RightInputType;
/// <summary>
/// The operator used by this binary operator instruction.
/// </summary>
public readonly BinaryNumericOperator Operator;
/// <summary>
/// Gets whether this conversion is a lifted nullable operation.
/// </summary>
/// <remarks>
/// A lifted binary operation allows its arguments to be a value of type Nullable{T}, where
/// T.GetStackType() == [Left|Right]InputType.
/// If both input values is non-null:
/// * they are sign/zero-extended to the corresponding InputType (based on T's sign)
/// * the underlying numeric operator is applied
/// * the result is wrapped in a Nullable{UnderlyingResultType}.
/// If either input is null, the instruction evaluates to default(UnderlyingResultType?).
/// (this result type is underspecified, since there may be multiple C# types for the stack type)
/// </remarks>
public bool IsLifted { get; set; }
readonly StackType resultType;
public BinaryNumericInstruction(BinaryNumericOperator op, ILInstruction left, ILInstruction right, bool checkForOverflow, Sign sign)
@ -64,7 +82,9 @@ namespace ICSharpCode.Decompiler.IL @@ -64,7 +82,9 @@ namespace ICSharpCode.Decompiler.IL
this.CheckForOverflow = checkForOverflow;
this.Sign = sign;
this.Operator = op;
this.resultType = ComputeResultType(op, left.ResultType, right.ResultType);
this.LeftInputType = left.ResultType;
this.RightInputType = right.ResultType;
this.resultType = ComputeResultType(op, LeftInputType, RightInputType);
Debug.Assert(resultType != StackType.Unknown);
}
@ -91,9 +111,18 @@ namespace ICSharpCode.Decompiler.IL @@ -91,9 +111,18 @@ namespace ICSharpCode.Decompiler.IL
return StackType.Unknown;
}
public StackType UnderlyingResultType { get => resultType; }
public sealed override StackType ResultType {
get {
return resultType;
get => IsLifted ? StackType.O : resultType;
}
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
if (!IsLifted) {
Debug.Assert(LeftInputType == Left.ResultType);
Debug.Assert(RightInputType == Right.ResultType);
}
}
@ -145,12 +174,17 @@ namespace ICSharpCode.Decompiler.IL @@ -145,12 +174,17 @@ namespace ICSharpCode.Decompiler.IL
{
output.Write(OpCode);
output.Write("." + GetOperatorName(Operator));
if (CheckForOverflow)
if (CheckForOverflow) {
output.Write(".ovf");
if (Sign == Sign.Unsigned)
}
if (Sign == Sign.Unsigned) {
output.Write(".unsigned");
else if (Sign == Sign.Signed)
} else if (Sign == Sign.Signed) {
output.Write(".signed");
}
if (IsLifted) {
output.Write(".lifted");
}
output.Write('(');
Left.WriteTo(output);
output.Write(", ");

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

@ -100,7 +100,7 @@ namespace ICSharpCode.Decompiler.IL @@ -100,7 +100,7 @@ namespace ICSharpCode.Decompiler.IL
/// * it is sign/zero-extended to InputType (based on T's sign)
/// * the underlying conversion is performed
/// * the result is wrapped in a Nullable{TargetType}.
/// If the value is null, the conversion evaluates to null of type Nullable{TargetType}.
/// If the value is null, the conversion evaluates to default(TargetType?).
/// (this result type is underspecified, since there may be multiple C# types for the TargetType)
/// </remarks>
public bool IsLifted { get; set; }

40
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -1,9 +1,22 @@ @@ -1,9 +1,22 @@
using System;
using System.Collections.Generic;
// Copyright (c) 2017 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
@ -60,6 +73,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -60,6 +73,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
conv.IsLifted = true;
return conv;
}
} else if (inst is BinaryNumericInstruction binary) {
if (SemanticHelper.IsPure(binary.Right.Flags)) {
var arg = LiftUnary(binary.Left, inputVar, context);
if (arg != null) {
binary.Left = arg;
binary.IsLifted = true;
return binary;
}
}
if (SemanticHelper.IsPure(binary.Left.Flags)) {
var arg = LiftUnary(binary.Right, inputVar, context);
if (arg != null) {
binary.Right = arg;
binary.IsLifted = true;
return binary;
}
}
}
return null;
}

Loading…
Cancel
Save