mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 11:09:54 +00:00
278 lines
9.1 KiB
C#
278 lines
9.1 KiB
C#
#nullable enable
|
|
// Copyright (c) 2014 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;
|
|
using System.Diagnostics;
|
|
|
|
using ICSharpCode.Decompiler.CSharp.Syntax;
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
|
|
namespace ICSharpCode.Decompiler.IL
|
|
{
|
|
public enum ComparisonKind : byte
|
|
{
|
|
Equality,
|
|
Inequality,
|
|
LessThan,
|
|
LessThanOrEqual,
|
|
GreaterThan,
|
|
GreaterThanOrEqual
|
|
}
|
|
|
|
static class ComparisonKindExtensions
|
|
{
|
|
public static bool IsEqualityOrInequality(this ComparisonKind kind)
|
|
{
|
|
return kind == ComparisonKind.Equality || kind == ComparisonKind.Inequality;
|
|
}
|
|
|
|
public static ComparisonKind Negate(this ComparisonKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case ComparisonKind.Equality:
|
|
return ComparisonKind.Inequality;
|
|
case ComparisonKind.Inequality:
|
|
return ComparisonKind.Equality;
|
|
case ComparisonKind.LessThan:
|
|
return ComparisonKind.GreaterThanOrEqual;
|
|
case ComparisonKind.LessThanOrEqual:
|
|
return ComparisonKind.GreaterThan;
|
|
case ComparisonKind.GreaterThan:
|
|
return ComparisonKind.LessThanOrEqual;
|
|
case ComparisonKind.GreaterThanOrEqual:
|
|
return ComparisonKind.LessThan;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
public static BinaryOperatorType ToBinaryOperatorType(this ComparisonKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case ComparisonKind.Equality:
|
|
return BinaryOperatorType.Equality;
|
|
case ComparisonKind.Inequality:
|
|
return BinaryOperatorType.InEquality;
|
|
case ComparisonKind.LessThan:
|
|
return BinaryOperatorType.LessThan;
|
|
case ComparisonKind.LessThanOrEqual:
|
|
return BinaryOperatorType.LessThanOrEqual;
|
|
case ComparisonKind.GreaterThan:
|
|
return BinaryOperatorType.GreaterThan;
|
|
case ComparisonKind.GreaterThanOrEqual:
|
|
return BinaryOperatorType.GreaterThanOrEqual;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
public static string GetToken(this ComparisonKind kind)
|
|
{
|
|
return BinaryOperatorExpression.GetOperatorToken(kind.ToBinaryOperatorType());
|
|
}
|
|
}
|
|
|
|
public enum ComparisonLiftingKind
|
|
{
|
|
/// <summary>
|
|
/// Not a lifted comparison.
|
|
/// </summary>
|
|
None,
|
|
/// <summary>
|
|
/// C#-style lifted comparison:
|
|
/// * operands that have a ResultType != this.InputType are expected to return a value of
|
|
/// type Nullable{T}, where T.GetStackType() == this.InputType.
|
|
/// * if both operands are <c>null</c>, equality comparisons evaluate to 1, all other comparisons to 0.
|
|
/// * if one operand is <c>null</c>, inequality comparisons evaluate to 1, all other comparisons to 0.
|
|
/// * if neither operand is <c>null</c>, the underlying comparison is performed.
|
|
///
|
|
/// Note that even though C#-style lifted comparisons set IsLifted=true,
|
|
/// the ResultType remains I4 as with normal comparisons.
|
|
/// </summary>
|
|
CSharp,
|
|
/// <summary>
|
|
/// SQL-style lifted comparison: works like a lifted binary numeric instruction,
|
|
/// that is, if any input operand is <c>null</c>, the comparison evaluates to <c>null</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This lifting kind is currently only used for operator! on bool?.
|
|
/// </remarks>
|
|
ThreeValuedLogic
|
|
}
|
|
|
|
partial class Comp : ILiftableInstruction
|
|
{
|
|
ComparisonKind kind;
|
|
|
|
public ComparisonKind Kind {
|
|
get { return kind; }
|
|
set {
|
|
kind = value;
|
|
MakeDirty();
|
|
}
|
|
}
|
|
|
|
public ComparisonLiftingKind LiftingKind;
|
|
|
|
/// <summary>
|
|
/// Gets the stack type of the comparison inputs.
|
|
/// For lifted comparisons, this is the underlying input type.
|
|
/// </summary>
|
|
public StackType InputType;
|
|
|
|
/// <summary>
|
|
/// If this is an integer comparison, specifies the sign used to interpret the integers.
|
|
/// </summary>
|
|
public readonly Sign Sign;
|
|
|
|
public Comp(ComparisonKind kind, Sign sign, ILInstruction left, ILInstruction right) : base(OpCode.Comp, left, right)
|
|
{
|
|
this.kind = kind;
|
|
this.LiftingKind = ComparisonLiftingKind.None;
|
|
this.InputType = left.ResultType;
|
|
this.Sign = sign;
|
|
Debug.Assert(left.ResultType == right.ResultType);
|
|
}
|
|
|
|
public Comp(ComparisonKind kind, ComparisonLiftingKind lifting, StackType inputType, Sign sign, ILInstruction left, ILInstruction right) : base(OpCode.Comp, left, right)
|
|
{
|
|
this.kind = kind;
|
|
this.LiftingKind = lifting;
|
|
this.InputType = inputType;
|
|
this.Sign = sign;
|
|
}
|
|
|
|
public override StackType ResultType => LiftingKind == ComparisonLiftingKind.ThreeValuedLogic ? StackType.VT : StackType.I4;
|
|
|
|
public override IType InferType(ICompilation compilation)
|
|
{
|
|
IType boolType = compilation.FindType(KnownTypeCode.Boolean);
|
|
if (LiftingKind == ComparisonLiftingKind.ThreeValuedLogic)
|
|
{
|
|
return NullableType.Create(compilation, boolType);
|
|
}
|
|
else
|
|
{
|
|
return boolType;
|
|
}
|
|
}
|
|
|
|
public bool IsLifted => LiftingKind != ComparisonLiftingKind.None;
|
|
public StackType UnderlyingResultType => StackType.I4;
|
|
|
|
internal override void CheckInvariant(ILPhase phase, ICompilation compilation)
|
|
{
|
|
base.CheckInvariant(phase, compilation);
|
|
if (LiftingKind == ComparisonLiftingKind.None)
|
|
{
|
|
// As a special case, we allow comparing a value type with null (but only in the non-lifted Comp).
|
|
// This is useful for unconstrained generics: these are treated as VT
|
|
// because they might be value types, but they can also be compared with null
|
|
// because they might be reference types. (the original IL uses `box` for this,
|
|
// but we drop the box in the ILAst to avoid a special case in PatternMatchingTransform etc.)
|
|
// TODO: these are unclean semantics; we should probably drop this special case and adjust the other transforms.
|
|
Debug.Assert(Left.ResultType == InputType || Left is LdNull);
|
|
Debug.Assert(Right.ResultType == InputType || Right is LdNull);
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(Left.ResultType == InputType || Left.ResultType == StackType.VT);
|
|
Debug.Assert(Right.ResultType == InputType || Right.ResultType == StackType.VT);
|
|
}
|
|
}
|
|
|
|
protected override void WriteToCore(ITextOutput output, ILAstWritingOptions options)
|
|
{
|
|
WriteILRange(output, options);
|
|
if (options.UseLogicOperationSugar && MatchLogicNot(out var arg))
|
|
{
|
|
output.Write("logic.not(");
|
|
arg.WriteTo(output, options);
|
|
output.Write(')');
|
|
return;
|
|
}
|
|
output.Write(OpCode);
|
|
output.Write('.');
|
|
output.Write(InputType.ToString().ToLower());
|
|
switch (Sign)
|
|
{
|
|
case Sign.Signed:
|
|
output.Write(".signed");
|
|
break;
|
|
case Sign.Unsigned:
|
|
output.Write(".unsigned");
|
|
break;
|
|
}
|
|
switch (LiftingKind)
|
|
{
|
|
case ComparisonLiftingKind.CSharp:
|
|
output.Write(".lifted[C#]");
|
|
break;
|
|
case ComparisonLiftingKind.ThreeValuedLogic:
|
|
output.Write(".lifted[3VL]");
|
|
break;
|
|
}
|
|
output.Write('(');
|
|
Left.WriteTo(output, options);
|
|
output.Write(' ');
|
|
output.Write(Kind.GetToken());
|
|
output.Write(' ');
|
|
Right.WriteTo(output, options);
|
|
output.Write(')');
|
|
}
|
|
|
|
public static Comp LogicNot(ILInstruction arg)
|
|
{
|
|
return new Comp(ComparisonKind.Equality, Sign.None, arg, new LdcI4(0));
|
|
}
|
|
|
|
public static Comp LogicNot(ILInstruction arg, bool isLifted)
|
|
{
|
|
var liftingKind = isLifted ? ComparisonLiftingKind.ThreeValuedLogic : ComparisonLiftingKind.None;
|
|
return new Comp(ComparisonKind.Equality, liftingKind, StackType.I4, Sign.None, arg, new LdcI4(0));
|
|
}
|
|
|
|
internal override bool CanInlineIntoSlot(int childIndex, ILInstruction expressionBeingMoved)
|
|
{
|
|
// ExpressionBuilder translates comp.o(a op b) for op not in (==, !=) into
|
|
// Unsafe.As(ref a) op Unsafe.As(ref b), which requires that a and b are variables
|
|
// and not expressions. Returning false in those cases prevents inlining.
|
|
// However if one of the arguments is LdNull, then we don't need the Unsafe.As trickery, and can always inline.
|
|
if (kind.IsEqualityOrInequality() || this.InputType != StackType.Obj)
|
|
{
|
|
// OK, won't need Unsafe.As.
|
|
return true;
|
|
}
|
|
if (expressionBeingMoved is LdLoc || expressionBeingMoved.MatchLdsFld(out _))
|
|
{
|
|
// OK, can use variable/field name with Unsafe.As(ref x)
|
|
return true;
|
|
}
|
|
if (Sign != Sign.Signed && (expressionBeingMoved is LdNull || Left is LdNull || Right is LdNull))
|
|
{
|
|
// OK, this is the "compare with null" special case that doesn't need Unsafe.As()
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|