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)