Beispiel #1
0
        /// <summary>
        /// stloc v(value)
        /// if (logic.not(call get_HasValue(ldloca v))) throw(...)
        /// ... Call(arg1, arg2, call GetValueOrDefault(ldloca v), arg4) ...
        /// =>
        /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ...
        /// </summary>
        bool TransformThrowExpressionValueTypes(Block block, int pos, StatementTransformContext context)
        {
            if (pos + 2 >= block.Instructions.Count)
            {
                return(false);
            }
            if (!(block.Instructions[pos] is StLoc stloc))
            {
                return(false);
            }
            ILVariable v = stloc.Variable;

            if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2))
            {
                return(false);
            }
            if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst))
            {
                return(false);
            }
            if (!(Block.Unwrap(trueInst) is Throw throwInst))
            {
                return(false);
            }
            if (!condition.MatchLogicNot(out var arg))
            {
                return(false);
            }
            if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, v)))
            {
                return(false);
            }
            var throwInstParent         = throwInst.Parent;
            var throwInstChildIndex     = throwInst.ChildIndex;
            var nullCoalescingWithThrow = new NullCoalescingInstruction(
                NullCoalescingKind.NullableWithValueFallback,
                stloc.Value,
                throwInst);
            var resultType = NullableType.GetUnderlyingType(call.Method.DeclaringType).GetStackType();

            nullCoalescingWithThrow.UnderlyingResultType = resultType;
            var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullCoalescingWithThrow, InliningOptions.None);

            if (result.Type == ILInlining.FindResultType.Found &&
                NullableLiftingTransform.MatchGetValueOrDefault(result.LoadInst.Parent, v))
            {
                context.Step("NullCoalescingTransform (value types + throw expression)", stloc);
                throwInst.resultType = resultType;
                result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow);
                block.Instructions.RemoveRange(pos, 2);                 // remove store(s) and if instruction
                return(true);
            }
            else
            {
                // reset the primary position (see remarks on ILInstruction.Parent)
                stloc.Value = stloc.Value;
                var children = throwInstParent.Children;
                children[throwInstChildIndex] = throwInst;
                return(false);
            }
        }
Beispiel #2
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);
        }
Beispiel #3
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
                    });
                }
            }
            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);
        }