diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 42c46ef8b..e18fd8b96 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -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);
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 0473b90df..73faa6583 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -276,6 +276,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index 4519ba671..802fe237c 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -93,6 +93,10 @@ namespace ICSharpCode.Decompiler.IL
StLoc,
/// Stores the value into an anonymous temporary variable, and returns the address of that variable.
AddressOf,
+ /// 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.
+ ThreeValuedLogicAnd,
+ /// 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.
+ ThreeValuedLogicOr,
/// Loads a constant string.
LdStr,
/// Loads a constant 32-bit integer.
@@ -2236,6 +2240,62 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
+{
+ /// 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.
+ 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(ILVisitor visitor)
+ {
+ return visitor.VisitThreeValuedLogicAnd(this);
+ }
+ public override T AcceptVisitor(ILVisitor 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
+{
+ /// 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.
+ 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(ILVisitor visitor)
+ {
+ return visitor.VisitThreeValuedLogicOr(this);
+ }
+ public override T AcceptVisitor(ILVisitor 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
{
/// Loads a constant string.
public sealed partial class LdStr : SimpleInstruction
@@ -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
{
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
{
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
"ldloca",
"stloc",
"addressof",
+ "3vl.logic.and",
+ "3vl.logic.or",
"ldstr",
"ldc.i4",
"ldc.i8",
@@ -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;
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index 1b23c30d7..332e43ff9 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -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.",
diff --git a/ICSharpCode.Decompiler/IL/Instructions/ThreeValuedLogicInstructions.cs b/ICSharpCode.Decompiler/IL/Instructions/ThreeValuedLogicInstructions.cs
new file mode 100644
index 000000000..9d85403d7
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Instructions/ThreeValuedLogicInstructions.cs
@@ -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);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
index f79ac4ebb..93812a5b2 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
@@ -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
) { 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(ref T a, ref T b)