Browse Source

[nullables] Lift three-valued logic operators ('&' and '|' on bool?)

pull/870/head
Daniel Grunwald 8 years ago
parent
commit
b7320b25e4
  1. 34
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  3. 110
      ICSharpCode.Decompiler/IL/Instructions.cs
  4. 6
      ICSharpCode.Decompiler/IL/Instructions.tt
  5. 49
      ICSharpCode.Decompiler/IL/Instructions/ThreeValuedLogicInstructions.cs
  6. 58
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

34
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -561,7 +561,39 @@ namespace ICSharpCode.Decompiler.CSharp @@ -561,7 +561,39 @@ namespace ICSharpCode.Decompiler.CSharp
BinaryOperatorExpression.GetLinqNodeType(op, false),
left.ResolveResult, right.ResolveResult));
}
protected internal override TranslatedExpression VisitThreeValuedLogicAnd(ThreeValuedLogicAnd inst, TranslationContext context)
{
return HandleThreeValuedLogic(inst, BinaryOperatorType.BitwiseAnd, ExpressionType.And);
}
protected internal override TranslatedExpression VisitThreeValuedLogicOr(ThreeValuedLogicOr inst, TranslationContext context)
{
return HandleThreeValuedLogic(inst, BinaryOperatorType.BitwiseOr, ExpressionType.Or);
}
TranslatedExpression HandleThreeValuedLogic(BinaryInstruction inst, BinaryOperatorType op, ExpressionType eop)
{
var left = Translate(inst.Left);
var right = Translate(inst.Right);
IType boolType = compilation.FindType(KnownTypeCode.Boolean);
IType nullableBoolType = NullableType.Create(compilation, boolType);
if (NullableType.IsNullable(left.Type)) {
left = left.ConvertTo(nullableBoolType, this);
if (NullableType.IsNullable(right.Type)) {
right = right.ConvertTo(nullableBoolType, this);
} else {
right = right.ConvertTo(boolType, this);
}
} else {
left = left.ConvertTo(boolType, this);
right = right.ConvertTo(nullableBoolType, this);
}
return new BinaryOperatorExpression(left.Expression, op, right.Expression)
.WithRR(new OperatorResolveResult(nullableBoolType, eop, null, true, new[] { left.ResolveResult, right.ResolveResult }))
.WithILInstruction(inst);
}
ExpressionWithResolveResult Assignment(TranslatedExpression left, TranslatedExpression right)
{
right = right.ConvertTo(left.Type, this, allowImplicitConversion: true);

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -276,6 +276,7 @@ @@ -276,6 +276,7 @@
<Compile Include="IL\Instructions\ILVariableCollection.cs" />
<Compile Include="IL\Instructions\LockInstruction.cs" />
<Compile Include="IL\Instructions\NullCoalescingInstruction.cs" />
<Compile Include="IL\Instructions\ThreeValuedLogicInstructions.cs" />
<Compile Include="IL\Patterns\AnyNode.cs" />
<Compile Include="IL\ControlFlow\YieldReturnDecompiler.cs" />
<Compile Include="IL\DetectedLoop.cs" />

110
ICSharpCode.Decompiler/IL/Instructions.cs

@ -93,6 +93,10 @@ namespace ICSharpCode.Decompiler.IL @@ -93,6 +93,10 @@ namespace ICSharpCode.Decompiler.IL
StLoc,
/// <summary>Stores the value into an anonymous temporary variable, and returns the address of that variable.</summary>
AddressOf,
/// <summary>Three valued logic and. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.and(), does not have short-circuiting behavior.</summary>
ThreeValuedLogicAnd,
/// <summary>Three valued logic or. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.or(), does not have short-circuiting behavior.</summary>
ThreeValuedLogicOr,
/// <summary>Loads a constant string.</summary>
LdStr,
/// <summary>Loads a constant 32-bit integer.</summary>
@ -2236,6 +2240,62 @@ namespace ICSharpCode.Decompiler.IL @@ -2236,6 +2240,62 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Three valued logic and. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.and(), does not have short-circuiting behavior.</summary>
public sealed partial class ThreeValuedLogicAnd : BinaryInstruction
{
public ThreeValuedLogicAnd(ILInstruction left, ILInstruction right) : base(OpCode.ThreeValuedLogicAnd, left, right)
{
}
public override StackType ResultType { get { return StackType.O; } }
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitThreeValuedLogicAnd(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitThreeValuedLogicAnd(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitThreeValuedLogicAnd(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as ThreeValuedLogicAnd;
return o != null && this.Left.PerformMatch(o.Left, ref match) && this.Right.PerformMatch(o.Right, ref match);
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Three valued logic or. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.or(), does not have short-circuiting behavior.</summary>
public sealed partial class ThreeValuedLogicOr : BinaryInstruction
{
public ThreeValuedLogicOr(ILInstruction left, ILInstruction right) : base(OpCode.ThreeValuedLogicOr, left, right)
{
}
public override StackType ResultType { get { return StackType.O; } }
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitThreeValuedLogicOr(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitThreeValuedLogicOr(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitThreeValuedLogicOr(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as ThreeValuedLogicOr;
return o != null && this.Left.PerformMatch(o.Left, ref match) && this.Right.PerformMatch(o.Right, ref match);
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Loads a constant string.</summary>
public sealed partial class LdStr : SimpleInstruction
@ -4290,6 +4350,14 @@ namespace ICSharpCode.Decompiler.IL @@ -4290,6 +4350,14 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitThreeValuedLogicAnd(ThreeValuedLogicAnd inst)
{
Default(inst);
}
protected internal virtual void VisitThreeValuedLogicOr(ThreeValuedLogicOr inst)
{
Default(inst);
}
protected internal virtual void VisitLdStr(LdStr inst)
{
Default(inst);
@ -4564,6 +4632,14 @@ namespace ICSharpCode.Decompiler.IL @@ -4564,6 +4632,14 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitThreeValuedLogicAnd(ThreeValuedLogicAnd inst)
{
return Default(inst);
}
protected internal virtual T VisitThreeValuedLogicOr(ThreeValuedLogicOr inst)
{
return Default(inst);
}
protected internal virtual T VisitLdStr(LdStr inst)
{
return Default(inst);
@ -4838,6 +4914,14 @@ namespace ICSharpCode.Decompiler.IL @@ -4838,6 +4914,14 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
protected internal virtual T VisitThreeValuedLogicAnd(ThreeValuedLogicAnd inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitThreeValuedLogicOr(ThreeValuedLogicOr inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitLdStr(LdStr inst, C context)
{
return Default(inst, context);
@ -5011,6 +5095,8 @@ namespace ICSharpCode.Decompiler.IL @@ -5011,6 +5095,8 @@ namespace ICSharpCode.Decompiler.IL
"ldloca",
"stloc",
"addressof",
"3vl.logic.and",
"3vl.logic.or",
"ldstr",
"ldc.i4",
"ldc.i8",
@ -5183,6 +5269,30 @@ namespace ICSharpCode.Decompiler.IL @@ -5183,6 +5269,30 @@ namespace ICSharpCode.Decompiler.IL
value = default(ILInstruction);
return false;
}
public bool MatchThreeValuedLogicAnd(out ILInstruction left, out ILInstruction right)
{
var inst = this as ThreeValuedLogicAnd;
if (inst != null) {
left = inst.Left;
right = inst.Right;
return true;
}
left = default(ILInstruction);
right = default(ILInstruction);
return false;
}
public bool MatchThreeValuedLogicOr(out ILInstruction left, out ILInstruction right)
{
var inst = this as ThreeValuedLogicOr;
if (inst != null) {
left = inst.Left;
right = inst.Right;
return true;
}
left = default(ILInstruction);
right = default(ILInstruction);
return false;
}
public bool MatchLdStr(out string value)
{
var inst = this as LdStr;

6
ICSharpCode.Decompiler/IL/Instructions.tt

@ -144,7 +144,11 @@ @@ -144,7 +144,11 @@
ResultType("variable.StackType")),
new OpCode("addressof", "Stores the value into an anonymous temporary variable, and returns the address of that variable.",
CustomClassName("AddressOf"), CustomArguments("value"), ResultType("Ref")),
new OpCode("3vl.logic.and", "Three valued logic and. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.and(), does not have short-circuiting behavior.",
CustomClassName("ThreeValuedLogicAnd"), Binary, ResultType("O")),
new OpCode("3vl.logic.or", "Three valued logic or. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.or(), does not have short-circuiting behavior.",
CustomClassName("ThreeValuedLogicOr"), Binary, ResultType("O")),
new OpCode("ldstr", "Loads a constant string.",
CustomClassName("LdStr"), LoadConstant("string"), ResultType("O")),
new OpCode("ldc.i4", "Loads a constant 32-bit integer.",

49
ICSharpCode.Decompiler/IL/Instructions/ThreeValuedLogicInstructions.cs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
// 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;
namespace ICSharpCode.Decompiler.IL
{
// Note: The comp instruction also supports three-valued logic via ComparisonLiftingKind.ThreeValuedLogic.
// comp.i4.lifted[3VL](x == ldc.i4 0) is used to represent a lifted logic.not.
partial class ThreeValuedLogicAnd : ILiftableInstruction
{
bool ILiftableInstruction.IsLifted => true;
StackType ILiftableInstruction.UnderlyingResultType => StackType.I4;
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
Debug.Assert(Left.ResultType == StackType.I4 || Left.ResultType == StackType.O);
}
}
partial class ThreeValuedLogicOr : ILiftableInstruction
{
bool ILiftableInstruction.IsLifted => true;
StackType ILiftableInstruction.UnderlyingResultType => StackType.I4;
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
Debug.Assert(Left.ResultType == StackType.I4 || Left.ResultType == StackType.O);
}
}
}

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

@ -157,7 +157,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -157,7 +157,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
if (MatchGetValueOrDefault(condition, out ILVariable v)
ILVariable v;
if (MatchGetValueOrDefault(condition, out v)
&& NullableType.GetUnderlyingType(v.Type).IsKnownType(KnownTypeCode.Boolean))
{
if (MatchHasValueCall(trueInst, v) && falseInst.MatchLdcI4(0)) {
@ -198,9 +199,62 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -198,9 +199,62 @@ namespace ICSharpCode.Decompiler.IL.Transforms
) { ILRange = ifInst.ILRange };
}
}
if (trueInst.MatchLdLoc(out v)) {
if (MatchNullableCtor(falseInst, out var utype, out var arg)
&& utype.IsKnownType(KnownTypeCode.Boolean) && arg.MatchLdcI4(0))
{
// condition ? v : (bool?)false
// => condition & v
context.Step("NullableLiftingTransform: 3vl.logic.and(bool, bool?)", ifInst);
return new ThreeValuedLogicAnd(condition, trueInst) { ILRange = ifInst.ILRange };
}
if (falseInst.MatchLdLoc(out var v2)) {
// condition ? v : v2
if (MatchThreeValuedLogicConditionPattern(condition, out var nullable1, out var nullable2)) {
// (nullable1.GetValueOrDefault() || (!nullable2.GetValueOrDefault() && !nullable1.HasValue)) ? v : v2
if (v == nullable1 && v2 == nullable2) {
context.Step("NullableLiftingTransform: 3vl.logic.or(bool?, bool?)", ifInst);
return new ThreeValuedLogicOr(trueInst, falseInst) { ILRange = ifInst.ILRange };
} else if (v == nullable2 && v2 == nullable1) {
context.Step("NullableLiftingTransform: 3vl.logic.and(bool?, bool?)", ifInst);
return new ThreeValuedLogicAnd(falseInst, trueInst) { ILRange = ifInst.ILRange };
}
}
}
} else if (falseInst.MatchLdLoc(out v)) {
if (MatchNullableCtor(trueInst, out var utype, out var arg)
&& utype.IsKnownType(KnownTypeCode.Boolean) && arg.MatchLdcI4(1)) {
// condition ? (bool?)true : v
// => condition | v
context.Step("NullableLiftingTransform: 3vl.logic.or(bool, bool?)", ifInst);
return new ThreeValuedLogicOr(condition, falseInst) { ILRange = ifInst.ILRange };
}
}
return null;
}
private bool MatchThreeValuedLogicConditionPattern(ILInstruction condition, out ILVariable nullable1, out ILVariable nullable2)
{
// Try to match: nullable1.GetValueOrDefault() || (!nullable2.GetValueOrDefault() && !nullable1.HasValue)
nullable1 = null;
nullable2 = null;
if (!condition.MatchLogicOr(out var lhs, out var rhs))
return false;
if (!MatchGetValueOrDefault(lhs, out nullable1))
return false;
if (!NullableType.GetUnderlyingType(nullable1.Type).IsKnownType(KnownTypeCode.Boolean))
return false;
if (!rhs.MatchLogicAnd(out lhs, out rhs))
return false;
if (!lhs.MatchLogicNot(out var arg))
return false;
if (!MatchGetValueOrDefault(arg, out nullable2))
return false;
if (!NullableType.GetUnderlyingType(nullable2.Type).IsKnownType(KnownTypeCode.Boolean))
return false;
if (!rhs.MatchLogicNot(out arg))
return false;
return MatchHasValueCall(arg, nullable1);
}
static void Swap<T>(ref T a, ref T b)

Loading…
Cancel
Save