Example #1
0
        ILInstruction Lift(IfInstruction ifInst, ILInstruction trueInst, ILInstruction falseInst)
        {
            ILInstruction condition = ifInst.Condition;

            while (condition.MatchLogicNot(out var arg))
            {
                condition = arg;
                Swap(ref trueInst, ref falseInst);
            }
            if (AnalyzeCondition(condition))
            {
                // (v1 != null && ... && vn != null) ? trueInst : falseInst
                // => normal lifting
                return(LiftNormal(trueInst, falseInst, ilrange: ifInst.ILRange));
            }
            if (condition is Comp comp && !comp.IsLifted && !comp.Kind.IsEqualityOrInequality())
            {
                // This might be a C#-style lifted comparison
                // (C# checks the underlying value before checking the HasValue bits)
                if (falseInst.MatchLdcI4(0) && AnalyzeCondition(trueInst))
                {
                    // comp(lhs, rhs) ? (v1 != null && ... && vn != null) : false
                    // => comp.lifted[C#](lhs, rhs)
                    return(LiftCSharpComparison(comp, comp.Kind));
                }
                else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst))
                {
                    // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null)
                    return(LiftCSharpComparison(comp, comp.Kind.Negate()));
                }
            }
            return(null);
        }
Example #2
0
 static bool IsNullOrZero(ILInstruction inst)
 {
     while (inst is Conv conv)
     {
         inst = conv.Argument;
     }
     return(inst.MatchLdcI4(0) || inst.MatchLdNull());
 }
Example #3
0
        static bool IsNullOrZero(ILInstruction inst)
        {
            var conv = inst as Conv;

            if (conv != null)
            {
                inst = conv.Argument;
            }
            return(inst.MatchLdcI4(0) || inst.MatchLdNull());
        }
Example #4
0
        /// <summary>
        /// Performs nullable lifting.
        ///
        /// Produces a lifted instruction with semantics equivalent to:
        ///   (v1 != null && ... && vn != null) ? trueInst : falseInst,
        /// where the v1,...,vn are the <c>this.nullableVars</c>.
        /// If lifting fails, returns <c>null</c>.
        /// </summary>
        ILInstruction LiftNormal(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange)
        {
            bool isNullCoalescingWithNonNullableFallback = false;

            if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift))
            {
                isNullCoalescingWithNonNullableFallback = true;
                utype      = context.TypeSystem.Compilation.FindType(trueInst.ResultType.ToKnownTypeCode());
                exprToLift = trueInst;
                if (nullableVars.Count == 1 && exprToLift.MatchLdLoc(nullableVars[0]))
                {
                    // v.HasValue ? ldloc v : fallback
                    // => v ?? fallback
                    context.Step("v.HasValue ? v : fallback => v ?? fallback", trueInst);
                    return(new NullCoalescingInstruction(NullCoalescingKind.Nullable, trueInst, falseInst)
                    {
                        UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType(),
                        ILRange = ilrange
                    });
                }
                else if (trueInst is Call call && !call.IsLifted &&
                         CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) &&
                         call.Method.Name != "op_Equality" && call.Method.Name != "op_Inequality" &&
                         falseInst.MatchLdcI4(0))
                {
                    // (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0)
                    var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method);
                    if (liftedOperator != null)
                    {
                        var(left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1]);
                        if (left != null && right != null && bits.All(0, nullableVars.Count))
                        {
                            return(new Call(liftedOperator)
                            {
                                Arguments = { left, right },
                                ConstrainedTo = call.ConstrainedTo,
                                ILRange = call.ILRange,
                                ILStackWasEmpty = call.ILStackWasEmpty,
                                IsTail = call.IsTail
                            });
                        }
                    }
                }
            }
            ILInstruction lifted;

            if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0]))
            {
                // v.HasValue ? call GetValueOrDefault(ldloca v) : fallback
                // => conv.nop.lifted(ldloc v) ?? fallback
                // This case is handled separately from DoLift() because
                // that doesn't introduce nop-conversions.
                context.Step("v.HasValue ? v.GetValueOrDefault() : fallback => v ?? fallback", trueInst);
                var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type);
                lifted = new LdLoc(nullableVars[0]);
                if (!inputUType.Equals(utype) && utype.ToPrimitiveType() != PrimitiveType.None)
                {
                    // While the ILAst allows implicit conversions between short and int
                    // (because both map to I4); it does not allow implicit conversions
                    // between short? and int? (structs of different types).
                    // So use 'conv.nop.lifted' to allow the conversion.
                    lifted = new Conv(
                        lifted,
                        inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(),
                        checkForOverflow: false,
                        isLifted: true
                        )
                    {
                        ILRange = ilrange
                    };
                }
            }
            else
            {
                context.Step("NullableLiftingTransform.DoLift", trueInst);
                BitSet bits;
                (lifted, bits) = DoLift(exprToLift);
                if (lifted == null)
                {
                    return(null);
                }
                if (!bits.All(0, nullableVars.Count))
                {
                    // don't lift if a nullableVar doesn't contribute to the result
                    return(null);
                }
                Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted &&
                             liftable.UnderlyingResultType == exprToLift.ResultType);
            }
            if (isNullCoalescingWithNonNullableFallback)
            {
                lifted = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, lifted, falseInst)
                {
                    UnderlyingResultType = exprToLift.ResultType,
                    ILRange = ilrange
                };
            }
            else if (!MatchNull(falseInst, utype))
            {
                // Normal lifting, but the falseInst isn't `default(utype?)`
                // => use the `??` operator to provide the fallback value.
                lifted = new NullCoalescingInstruction(NullCoalescingKind.Nullable, lifted, falseInst)
                {
                    UnderlyingResultType = exprToLift.ResultType,
                    ILRange = ilrange
                };
            }
            return(lifted);
        }
Example #5
0
        ILInstruction Lift(IfInstruction ifInst, ILInstruction trueInst, ILInstruction falseInst)
        {
            ILInstruction condition = ifInst.Condition;

            while (condition.MatchLogicNot(out var arg))
            {
                condition = arg;
                Swap(ref trueInst, ref falseInst);
            }
            if (AnalyzeCondition(condition))
            {
                // (v1 != null && ... && vn != null) ? trueInst : falseInst
                // => normal lifting
                return(LiftNormal(trueInst, falseInst, ilrange: ifInst.ILRange));
            }
            if (condition is Comp comp && !comp.IsLifted)
            {
                // This might be a C#-style lifted comparison
                // (C# checks the underlying value before checking the HasValue bits)
                if (comp.Kind.IsEqualityOrInequality())
                {
                    // for equality/inequality, the HasValue bits must also compare equal/inequal
                    if (comp.Kind == ComparisonKind.Inequality)
                    {
                        // handle inequality by swapping one last time
                        Swap(ref trueInst, ref falseInst);
                    }
                    if (falseInst.MatchLdcI4(0))
                    {
                        // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue == b.HasValue) : false
                        // => a == b
                        return(LiftCSharpEqualityComparison(comp, ComparisonKind.Equality, trueInst));
                    }
                    else if (falseInst.MatchLdcI4(1))
                    {
                        // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue != b.HasValue) : true
                        // => a != b
                        return(LiftCSharpEqualityComparison(comp, ComparisonKind.Inequality, trueInst));
                    }
                    else if (IsGenericNewPattern(condition, trueInst, falseInst))
                    {
                        // (default(T) == null) ? Activator.CreateInstance<T>() : default(T)
                        // => Activator.CreateInstance<T>()
                        return(trueInst);
                    }
                }
                else
                {
                    // Not (in)equality, but one of < <= > >=.
                    // Returns false unless all HasValue bits are true.
                    if (falseInst.MatchLdcI4(0) && AnalyzeCondition(trueInst))
                    {
                        // comp(lhs, rhs) ? (v1 != null && ... && vn != null) : false
                        // => comp.lifted[C#](lhs, rhs)
                        return(LiftCSharpComparison(comp, comp.Kind));
                    }
                    else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst))
                    {
                        // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null)
                        return(LiftCSharpComparison(comp, comp.Kind.Negate()));
                    }
                }
            }
            ILVariable v;

            if (MatchGetValueOrDefault(condition, out v) &&
                NullableType.GetUnderlyingType(v.Type).IsKnownType(KnownTypeCode.Boolean))
            {
                if (MatchHasValueCall(trueInst, v) && falseInst.MatchLdcI4(0))
                {
                    // v.GetValueOrDefault() ? v.HasValue : false
                    // ==> v == true
                    context.Step("NullableLiftingTransform: v == true", ifInst);
                    return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v)
                    {
                        ILRange = trueInst.ILRange
                    },
                                    new LdcI4(1)
                    {
                        ILRange = falseInst.ILRange
                    }
                                    )
                    {
                        ILRange = ifInst.ILRange
                    });
                }
                else if (trueInst.MatchLdcI4(0) && MatchHasValueCall(falseInst, v))
                {
                    // v.GetValueOrDefault() ? false : v.HasValue
                    // ==> v == false
                    context.Step("NullableLiftingTransform: v == false", ifInst);
                    return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v)
                    {
                        ILRange = falseInst.ILRange
                    },
                                    trueInst             // LdcI4(0)
                                    )
                    {
                        ILRange = ifInst.ILRange
                    });
                }
                else if (MatchNegatedHasValueCall(trueInst, v) && falseInst.MatchLdcI4(1))
                {
                    // v.GetValueOrDefault() ? !v.HasValue : true
                    // ==> v != true
                    context.Step("NullableLiftingTransform: v != true", ifInst);
                    return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v)
                    {
                        ILRange = trueInst.ILRange
                    },
                                    falseInst             // LdcI4(1)
                                    )
                    {
                        ILRange = ifInst.ILRange
                    });
                }
                else if (trueInst.MatchLdcI4(1) && MatchNegatedHasValueCall(falseInst, v))
                {
                    // v.GetValueOrDefault() ? true : !v.HasValue
                    // ==> v != false
                    context.Step("NullableLiftingTransform: v != false", ifInst);
                    return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v)
                    {
                        ILRange = falseInst.ILRange
                    },
                                    new LdcI4(0)
                    {
                        ILRange = trueInst.ILRange
                    }
                                    )
                    {
                        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);
        }
Example #6
0
        /// <summary>
        /// Main entry point for lifting; called by both the expression-transform
        /// and the block transform.
        /// </summary>
        ILInstruction Lift(ILInstruction ifInst, ILInstruction condition, ILInstruction trueInst, ILInstruction falseInst)
        {
            // ifInst is usually the IfInstruction to which condition belongs;
            // but can also be a BinaryNumericInstruction.
            while (condition.MatchLogicNot(out var arg))
            {
                condition = arg;
                ExtensionMethods.Swap(ref trueInst, ref falseInst);
            }
            if (context.Settings.NullPropagation && !NullPropagationTransform.IsProtectedIfInst(ifInst as IfInstruction))
            {
                var nullPropagated = new NullPropagationTransform(context)
                                     .Run(condition, trueInst, falseInst)?.WithILRange(ifInst);
                if (nullPropagated != null)
                {
                    return(nullPropagated);
                }
            }
            if (!context.Settings.LiftNullables)
            {
                return(null);
            }
            if (AnalyzeCondition(condition))
            {
                // (v1 != null && ... && vn != null) ? trueInst : falseInst
                // => normal lifting
                return(LiftNormal(trueInst, falseInst)?.WithILRange(ifInst));
            }
            if (MatchCompOrDecimal(condition, out var comp))
            {
                // This might be a C#-style lifted comparison
                // (C# checks the underlying value before checking the HasValue bits)
                if (comp.Kind.IsEqualityOrInequality())
                {
                    // for equality/inequality, the HasValue bits must also compare equal/inequal
                    if (comp.Kind == ComparisonKind.Inequality)
                    {
                        // handle inequality by swapping one last time
                        ExtensionMethods.Swap(ref trueInst, ref falseInst);
                    }
                    if (falseInst.MatchLdcI4(0))
                    {
                        // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue == b.HasValue) : false
                        // => a == b
                        return(LiftCSharpEqualityComparison(comp, ComparisonKind.Equality, trueInst)
                               ?? LiftCSharpUserEqualityComparison(comp, ComparisonKind.Equality, trueInst));
                    }
                    else if (falseInst.MatchLdcI4(1))
                    {
                        // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue != b.HasValue) : true
                        // => a != b
                        return(LiftCSharpEqualityComparison(comp, ComparisonKind.Inequality, trueInst)
                               ?? LiftCSharpUserEqualityComparison(comp, ComparisonKind.Inequality, trueInst));
                    }
                    else if (IsGenericNewPattern(comp.Left, comp.Right, trueInst, falseInst))
                    {
                        // (default(T) == null) ? Activator.CreateInstance<T>() : default(T)
                        // => Activator.CreateInstance<T>()
                        return(trueInst);
                    }
                }
                else
                {
                    // Not (in)equality, but one of < <= > >=.
                    // Returns false unless all HasValue bits are true.
                    if (falseInst.MatchLdcI4(0) && AnalyzeCondition(trueInst))
                    {
                        // comp(lhs, rhs) ? (v1 != null && ... && vn != null) : false
                        // => comp.lifted[C#](lhs, rhs)
                        return(LiftCSharpComparison(comp, comp.Kind));
                    }
                    else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst))
                    {
                        // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null)
                        return(LiftCSharpComparison(comp, comp.Kind.Negate()));
                    }
                }
            }
            ILVariable v;

            // Handle equality comparisons with bool?:
            if (MatchGetValueOrDefault(condition, out v) &&
                NullableType.GetUnderlyingType(v.Type).IsKnownType(KnownTypeCode.Boolean))
            {
                if (MatchHasValueCall(trueInst, v) && falseInst.MatchLdcI4(0))
                {
                    // v.GetValueOrDefault() ? v.HasValue : false
                    // ==> v == true
                    context.Step("NullableLiftingTransform: v == true", ifInst);
                    return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v).WithILRange(trueInst),
                                    new LdcI4(1).WithILRange(falseInst)
                                    ).WithILRange(ifInst));
                }
                else if (trueInst.MatchLdcI4(0) && MatchHasValueCall(falseInst, v))
                {
                    // v.GetValueOrDefault() ? false : v.HasValue
                    // ==> v == false
                    context.Step("NullableLiftingTransform: v == false", ifInst);
                    return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v).WithILRange(falseInst),
                                    trueInst             // LdcI4(0)
                                    ).WithILRange(ifInst));
                }
                else if (MatchNegatedHasValueCall(trueInst, v) && falseInst.MatchLdcI4(1))
                {
                    // v.GetValueOrDefault() ? !v.HasValue : true
                    // ==> v != true
                    context.Step("NullableLiftingTransform: v != true", ifInst);
                    return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v).WithILRange(trueInst),
                                    falseInst             // LdcI4(1)
                                    ).WithILRange(ifInst));
                }
                else if (trueInst.MatchLdcI4(1) && MatchNegatedHasValueCall(falseInst, v))
                {
                    // v.GetValueOrDefault() ? true : !v.HasValue
                    // ==> v != false
                    context.Step("NullableLiftingTransform: v != false", ifInst);
                    return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp,
                                    StackType.I4, Sign.None,
                                    new LdLoc(v).WithILRange(falseInst),
                                    new LdcI4(0).WithILRange(trueInst)
                                    ).WithILRange(ifInst));
                }
            }
            // Handle & and | on bool?:
            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.bool.and(bool, bool?)", ifInst);
                    return(new ThreeValuedBoolAnd(condition, trueInst).WithILRange(ifInst));
                }
                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.bool.or(bool?, bool?)", ifInst);
                            return(new ThreeValuedBoolOr(trueInst, falseInst).WithILRange(ifInst));
                        }
                        else if (v == nullable2 && v2 == nullable1)
                        {
                            context.Step("NullableLiftingTransform: 3vl.bool.and(bool?, bool?)", ifInst);
                            return(new ThreeValuedBoolAnd(falseInst, trueInst).WithILRange(ifInst));
                        }
                    }
                }
            }
            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 ThreeValuedBoolOr(condition, falseInst).WithILRange(ifInst));
                }
            }
            return(null);
        }