Skip to content

Commit

Permalink
[nullables] Add support for lifted binary operators where one of the …
Browse files Browse the repository at this point in the history
…inputs is nullable.
  • Loading branch information
dgrunwald committed Sep 18, 2017
1 parent 8671d32 commit e266c63
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 42 deletions.
2 changes: 2 additions & 0 deletions BuildTools/tidy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env python

import os, sys

def check(filename):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,12 @@ class LiftedImplicitConversions

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)
Expand Down
68 changes: 45 additions & 23 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,7 @@ protected internal override TranslatedExpression VisitNewArr(NewArr inst, Transl
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);
}
Expand Down Expand Up @@ -599,18 +598,18 @@ TranslatedExpression HandleBinaryNumeric(BinaryNumericInstruction inst, BinaryOp
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)
Expand All @@ -624,18 +623,28 @@ TranslatedExpression HandleBinaryNumeric(BinaryNumericInstruction inst, BinaryOp
/// <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;
}
Expand All @@ -646,7 +655,7 @@ TranslatedExpression PrepareArithmeticArgument(TranslatedExpression arg, StackTy
/// </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)
Expand All @@ -669,32 +678,45 @@ TranslatedExpression HandleShift(BinaryNumericInstruction inst, BinaryOperatorTy
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;
}
Expand Down Expand Up @@ -731,7 +753,7 @@ TranslatedExpression HandleCompoundAssignment(CompoundAssignmentInstruction inst
{
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) {
Expand Down
22 changes: 19 additions & 3 deletions ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
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;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ICSharpCode.Decompiler.IL.ControlFlow
{
Expand Down
52 changes: 43 additions & 9 deletions ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public enum BinaryNumericOperator : byte
ShiftRight
}

public partial class BinaryNumericInstruction : BinaryInstruction
public partial class BinaryNumericInstruction : BinaryInstruction, ILiftableInstruction
{
/// <summary>
/// Gets whether the instruction checks for overflow.
Expand All @@ -50,12 +50,30 @@ public partial class BinaryNumericInstruction : BinaryInstruction
/// For instructions that produce the same result for either sign, returns Sign.None.
/// </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)
Expand All @@ -64,7 +82,9 @@ public BinaryNumericInstruction(BinaryNumericOperator op, ILInstruction left, 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);
}

Expand All @@ -91,9 +111,18 @@ internal static StackType ComputeResultType(BinaryNumericOperator op, StackType
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);
}
}

Expand Down Expand Up @@ -145,12 +174,17 @@ public override void WriteTo(ITextOutput output)
{
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(", ");
Expand Down
2 changes: 1 addition & 1 deletion ICSharpCode.Decompiler/IL/Instructions/Conv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ partial class Conv : UnaryInstruction, ILiftableInstruction
/// * 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; }
Expand Down
40 changes: 35 additions & 5 deletions ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -60,6 +73,23 @@ static ILInstruction LiftUnary(ILInstruction inst, ILVariable inputVar, ILTransf
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;
}
Expand Down

0 comments on commit e266c63

Please sign in to comment.