Пример #1
0
        bool DoTransform(Block body, int pos)
        {
            if (pos >= body.Instructions.Count - 2)
            {
                return(false);
            }
            ILInstruction inst = body.Instructions[pos];

            if (inst.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var elementType, out var arrayLength))
            {
                if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out var values, out var initArrayPos))
                {
                    context.Step("ForwardScanInitializeArrayRuntimeHelper", inst);
                    var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                    var block     = BlockFromInitializer(tempStore, elementType, arrayLength, values);
                    body.Instructions[pos] = new StLoc(v, block);
                    body.Instructions.RemoveAt(initArrayPos);
                    ILInlining.InlineIfPossible(body, pos, context);
                    return(true);
                }
                if (arrayLength.Length == 1)
                {
                    if (HandleSimpleArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out values, out var instructionsToRemove))
                    {
                        context.Step("HandleSimpleArrayInitializer", inst);
                        var block     = new Block(BlockKind.ArrayInitializer);
                        var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                        block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
                        block.Instructions.AddRange(values.SelectWithIndex(
                                                        (i, value) => {
                            if (value == null)
                            {
                                value = GetNullExpression(elementType);
                            }
                            return(StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType));
                        }
                                                        ));
                        block.FinalInstruction = new LdLoc(tempStore);
                        body.Instructions[pos] = new StLoc(v, block);
                        body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
                        ILInlining.InlineIfPossible(body, pos, context);
                        return(true);
                    }
                    if (HandleJaggedArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out ILVariable finalStore, out values, out instructionsToRemove))
                    {
                        context.Step("HandleJaggedArrayInitializer", inst);
                        var block     = new Block(BlockKind.ArrayInitializer);
                        var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                        block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
                        block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType)));
                        block.FinalInstruction = new LdLoc(tempStore);
                        body.Instructions[pos] = new StLoc(finalStore, block);
                        body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
                        ILInlining.InlineIfPossible(body, pos, context);
                        return(true);
                    }
                }
            }
            return(false);
        }
Пример #2
0
        /// <summary>
        /// stloc v(value)
        /// expr(..., deconstruct { ... }, ...)
        /// =>
        /// expr(..., deconstruct { init: stloc v(value) ... }, ...)
        /// </summary>
        bool InlineDeconstructionInitializer(Block block, int pos)
        {
            if (!block.Instructions[pos].MatchStLoc(out var v, out var value))
            {
                return(false);
            }
            if (!(v.IsSingleDefinition && v.LoadCount == 1))
            {
                return(false);
            }
            if (pos + 1 >= block.Instructions.Count)
            {
                return(false);
            }
            var result = ILInlining.FindLoadInNext(block.Instructions[pos + 1], v, value, InliningOptions.FindDeconstruction);

            if (result.Type != ILInlining.FindResultType.Deconstruction)
            {
                return(false);
            }
            var   deconstruction = (DeconstructInstruction)result.LoadInst;
            LdLoc loadInst       = v.LoadInstructions[0];

            if (!loadInst.IsDescendantOf(deconstruction.Assignments))
            {
                return(false);
            }
            if (loadInst.SlotInfo == StObj.TargetSlot)
            {
                if (value.OpCode == OpCode.LdFlda || value.OpCode == OpCode.LdElema)
                {
                    return(false);
                }
            }
            if (deconstruction.Init.Count > 0)
            {
                var a = deconstruction.Init[0].Variable.LoadInstructions.Single();
                var b = v.LoadInstructions.Single();
                if (!b.IsBefore(a))
                {
                    return(false);
                }
            }
            context.Step("InlineDeconstructionInitializer", block.Instructions[pos]);
            deconstruction.Init.Insert(0, (StLoc)block.Instructions[pos]);
            block.Instructions.RemoveAt(pos);
            v.Kind = VariableKind.DeconstructionInitTemporary;
            return(true);
        }
Пример #3
0
 /// <summary>
 /// Handles NullCoalescingInstruction case 1: reference types.
 ///
 /// stloc s(valueInst)
 /// if (comp(ldloc s == ldnull)) {
 ///		stloc s(fallbackInst)
 /// }
 /// =>
 /// stloc s(if.notnull(valueInst, fallbackInst))
 /// </summary>
 bool TransformRefTypes(Block block, int pos, StatementTransformContext context)
 {
     if (!(block.Instructions[pos] is StLoc stloc))
     {
         return(false);
     }
     if (stloc.Variable.Kind != VariableKind.StackSlot)
     {
         return(false);
     }
     if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst))
     {
         return(false);
     }
     if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull()))
     {
         return(false);
     }
     trueInst = Block.Unwrap(trueInst);
     if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue))
     {
         context.Step("NullCoalescingTransform: simple (reference types)", stloc);
         stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue);
         block.Instructions.RemoveAt(pos + 1);                 // remove if instruction
         ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context);
         return(true);
     }
     // sometimes the compiler generates:
     // stloc s(valueInst)
     // if (comp(ldloc s == ldnull)) {
     //		stloc v(fallbackInst)
     //      stloc s(ldloc v)
     // }
     // v must be single-assign and single-use.
     if (trueInst is Block trueBlock && trueBlock.Instructions.Count == 2 &&
         trueBlock.Instructions[0].MatchStLoc(out var temporary, out fallbackValue) &&
         temporary.IsSingleDefinition && temporary.LoadCount == 1 &&
         trueBlock.Instructions[1].MatchStLoc(stloc.Variable, out var useOfTemporary) &&
         useOfTemporary.MatchLdLoc(temporary))
     {
         context.Step("NullCoalescingTransform: with temporary variable (reference types)", stloc);
         stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue);
         block.Instructions.RemoveAt(pos + 1);                 // remove if instruction
         ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context);
         return(true);
     }
     return(false);
 }
Пример #4
0
 /// <summary>
 /// Handles NullCoalescingInstruction case 1: reference types.
 ///
 /// stloc s(valueInst)
 /// if (comp(ldloc s == ldnull)) {
 ///		stloc s(fallbackInst)
 /// }
 /// =>
 /// stloc s(if.notnull(valueInst, fallbackInst))
 /// </summary>
 bool TransformRefTypes(Block block, int pos, StatementTransformContext context)
 {
     if (!(block.Instructions[pos] is StLoc stloc))
     {
         return(false);
     }
     if (stloc.Variable.Kind != VariableKind.StackSlot)
     {
         return(false);
     }
     if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst))
     {
         return(false);
     }
     trueInst = Block.Unwrap(trueInst);
     if (condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull() &&
         trueInst.MatchStLoc(stloc.Variable, out var fallbackValue)
         )
     {
         context.Step("NullCoalescingTransform (reference types)", stloc);
         stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue);
         block.Instructions.RemoveAt(pos + 1);                 // remove if instruction
         ILInlining.InlineOneIfPossible(block, pos, false, context);
         return(true);
     }
     return(false);
 }
Пример #5
0
 internal static void TransformDynamicSetMemberInstruction(DynamicSetMemberInstruction inst, StatementTransformContext context)
 {
     if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment))
     {
         return;
     }
     if (!(inst.Value is DynamicBinaryOperatorInstruction binaryOp))
     {
         return;
     }
     if (!(binaryOp.Left is DynamicGetMemberInstruction dynamicGetMember))
     {
         return;
     }
     if (!dynamicGetMember.Target.Match(inst.Target).Success)
     {
         return;
     }
     if (!SemanticHelper.IsPure(dynamicGetMember.Target.Flags))
     {
         return;
     }
     if (inst.Name != dynamicGetMember.Name || !DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation))
     {
         return;
     }
     context.Step("dynamic.setmember.compound -> dynamic.compound.op", inst);
     inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo));
 }
Пример #6
0
        bool LegacyPattern(Block block, int pos, StatementTransformContext context)
        {
            // Legacy csc pattern:
            //   stloc s(lhsInst)
            //   if (logic.not(call op_False(ldloc s))) Block {
            //     stloc s(call op_BitwiseAnd(ldloc s, rhsInst))
            //   }
            // ->
            //   stloc s(user.logic op_BitwiseAnd(lhsInst, rhsInst))
            if (!block.Instructions[pos].MatchStLoc(out var s, out var lhsInst))
            {
                return(false);
            }
            if (!(s.Kind == VariableKind.StackSlot))
            {
                return(false);
            }
            if (!(block.Instructions[pos + 1] is IfInstruction ifInst))
            {
                return(false);
            }
            if (!ifInst.Condition.MatchLogicNot(out var condition))
            {
                return(false);
            }
            if (!(MatchCondition(condition, out var s2, out string conditionMethodName) && s2 == s))
            {
                return(false);
            }
            if (ifInst.FalseInst.OpCode != OpCode.Nop)
            {
                return(false);
            }
            var trueInst = Block.Unwrap(ifInst.TrueInst);

            if (!trueInst.MatchStLoc(s, out var storeValue))
            {
                return(false);
            }
            if (storeValue is Call call)
            {
                if (!MatchBitwiseCall(call, s, conditionMethodName))
                {
                    return(false);
                }
                if (s.IsUsedWithin(call.Arguments[1]))
                {
                    return(false);
                }
                context.Step("User-defined short-circuiting logic operator (legacy pattern)", condition);
                ((StLoc)block.Instructions[pos]).Value = new UserDefinedLogicOperator(call.Method, lhsInst, call.Arguments[1])
                {
                    ILRange = call.ILRange
                };
                block.Instructions.RemoveAt(pos + 1);
                context.RequestRerun();                 // the 'stloc s' may now be eligible for inlining
                return(true);
            }
            return(false);
        }
Пример #7
0
        /// <summary>
        /// Transform compound assignments where the return value is not being used,
        /// or where there's an inlined assignment within the setter call.
        /// </summary>
        /// <remarks>
        /// Called by ExpressionTransforms.
        /// </remarks>
        internal static bool HandleCallCompoundAssign(CallInstruction setterCall, StatementTransformContext context)
        {
            // callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value))
            // ==> compound.op.new(callvirt get_Property(ldloc S_1), value)
            var setterValue   = setterCall.Arguments.LastOrDefault();
            var storeInSetter = setterValue as StLoc;

            if (storeInSetter != null)
            {
                // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
                // ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value))
                setterValue = storeInSetter.Value;
            }
            setterValue = UnwrapSmallIntegerConv(setterValue, out var conv);
            if (!(setterValue is BinaryNumericInstruction binary))
            {
                return(false);
            }
            var getterCall = binary.Left as CallInstruction;

            if (!MatchingGetterAndSetterCalls(getterCall, setterCall))
            {
                return(false);
            }
            IType targetType = getterCall.Method.ReturnType;

            if (!ValidateCompoundAssign(binary, conv, targetType))
            {
                return(false);
            }
            if (storeInSetter != null && storeInSetter.Variable.Type.IsSmallIntegerType())
            {
                // 'stloc v' implicitly truncates.
                // Ensure that type of 'v' must match type of the property:
                if (storeInSetter.Variable.Type.GetSize() != targetType.GetSize())
                {
                    return(false);
                }
                if (storeInSetter.Variable.Type.GetSign() != targetType.GetSign())
                {
                    return(false);
                }
            }
            context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall);
            ILInstruction newInst = new CompoundAssignmentInstruction(
                binary, getterCall, binary.Right,
                getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue);

            if (storeInSetter != null)
            {
                storeInSetter.Value = newInst;
                newInst             = storeInSetter;
                context.RequestRerun();                 // moving stloc to top-level might trigger inlining
            }
            setterCall.ReplaceWith(newInst);
            return(true);
        }
        /// stloc V_1(dynamic.isevent (target))
        /// if (logic.not(ldloc V_1)) Block IL_004a {
        ///     stloc V_2(dynamic.getmember B(target))
        /// }
        /// [stloc copyOfValue(value)]
        /// if (logic.not(ldloc V_1)) Block IL_0149 {
        ///     dynamic.setmember.compound B(target, dynamic.binary.operator AddAssign(ldloc V_2,  value))
        /// } else Block IL_0151 {
        ///     dynamic.invokemember.invokespecial.discard add_B(target, value)
        /// }
        /// =>
        /// if (logic.not(dynamic.isevent (target))) Block IL_0149 {
        ///     dynamic.setmember.compound B(target, dynamic.binary.operator AddAssign(dynamic.getmember B(target),  value))
        /// } else Block IL_0151 {
        ///     dynamic.invokemember.invokespecial.discard add_B(target, value)
        /// }
        public void Run(Block block, int pos, StatementTransformContext context)
        {
            if (!(pos + 3 < block.Instructions.Count && block.Instructions[pos].MatchStLoc(out var flagVar, out var inst) && inst is DynamicIsEventInstruction isEvent))
            {
                return;
            }
            if (!(flagVar.IsSingleDefinition && flagVar.LoadCount == 2))
            {
                return;
            }
            if (!MatchLhsCacheIfInstruction(block.Instructions[pos + 1], flagVar, out var dynamicGetMemberStore))
            {
                return;
            }
            if (!(dynamicGetMemberStore.MatchStLoc(out var getMemberVar, out inst) && inst is DynamicGetMemberInstruction getMemberInst))
            {
                return;
            }
            int offset = 2;

            if (block.Instructions[pos + offset].MatchStLoc(out var valueVariable) &&
                pos + 4 < block.Instructions.Count && valueVariable.IsSingleDefinition && valueVariable.LoadCount == 2 &&
                valueVariable.LoadInstructions.All(ld => ld.Parent is DynamicInstruction))
            {
                offset++;
            }
            foreach (var descendant in block.Instructions[pos + offset].Descendants)
            {
                if (!MatchIsEventAssignmentIfInstruction(descendant, isEvent, flagVar, getMemberVar, out var setMemberInst, out var getMemberVarUse, out var isEventConditionUse))
                {
                    continue;
                }
                context.Step("DynamicIsEventAssignmentTransform", block.Instructions[pos]);
                // Collapse duplicate condition
                getMemberVarUse.ReplaceWith(getMemberInst);
                isEventConditionUse.ReplaceWith(isEvent);
                block.Instructions.RemoveRange(pos, 2);
                // Reuse ExpressionTransforms
                ExpressionTransforms.TransformDynamicSetMemberInstruction(setMemberInst, context);
                context.RequestRerun();
                break;
            }
        }
Пример #9
0
        /// <summary>
        /// Transform compound assignments where the return value is not being used,
        /// or where there's an inlined assignment within the setter call.
        /// </summary>
        /// <remarks>
        /// Called by ExpressionTransforms.
        /// </remarks>
        internal static bool HandleCallCompoundAssign(CallInstruction setterCall, StatementTransformContext context)
        {
            // callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value))
            // ==> compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value)
            var setterValue   = setterCall.Arguments.LastOrDefault();
            var storeInSetter = setterValue as StLoc;

            if (storeInSetter != null)
            {
                // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
                // ==> stloc v(compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value))
                setterValue = storeInSetter.Value;
            }
            if (!(setterValue is BinaryNumericInstruction binary))
            {
                return(false);
            }
            var getterCall = binary.Left as CallInstruction;

            if (!MatchingGetterAndSetterCalls(getterCall, setterCall))
            {
                return(false);
            }
            if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType))
            {
                return(false);
            }
            context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall);
            ILInstruction newInst = new CompoundAssignmentInstruction(
                binary, getterCall, binary.Right,
                getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue);

            if (storeInSetter != null)
            {
                storeInSetter.Value = newInst;
                newInst             = storeInSetter;
                context.RequestRerun();                 // moving stloc to top-level might trigger inlining
            }
            setterCall.ReplaceWith(newInst);
            return(true);
        }
Пример #10
0
 bool RoslynOptimized(Block block, int pos, StatementTransformContext context)
 {
     // Roslyn, optimized pattern in combination with return statement:
     //   if (logic.not(call op_False(ldloc lhsVar))) leave IL_0000 (call op_BitwiseAnd(ldloc lhsVar, rhsInst))
     //   leave IL_0000(ldloc lhsVar)
     // ->
     //   user.logic op_BitwiseAnd(ldloc lhsVar, rhsInst)
     if (!block.Instructions[pos].MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst))
     {
         return(false);
     }
     if (trueInst.OpCode == OpCode.Nop)
     {
         trueInst = block.Instructions[pos + 1];
     }
     else if (falseInst.OpCode == OpCode.Nop)
     {
         falseInst = block.Instructions[pos + 1];
     }
     else
     {
         return(false);
     }
     if (trueInst.MatchReturn(out var trueValue) && falseInst.MatchReturn(out var falseValue))
     {
         var transformed = Transform(condition, trueValue, falseValue);
         if (transformed == null)
         {
             transformed = TransformDynamic(condition, trueValue, falseValue);
         }
         if (transformed != null)
         {
             context.Step("User-defined short-circuiting logic operator (optimized return)", condition);
             ((Leave)block.Instructions[pos + 1]).Value = transformed;
             block.Instructions.RemoveAt(pos);
             return(true);
         }
     }
     return(false);
 }
Пример #11
0
        bool DoTransform(Block body, int pos)
        {
            ILInstruction inst = body.Instructions[pos];

            // Match stloc(v, newobj)
            if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot))
            {
                IType instType;
                switch (initInst)
                {
                case NewObj newObjInst:
                    if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor && !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
                    {
                        // on statement level (no other expressions on IL stack),
                        // prefer to keep local variables (but not stack slots),
                        // unless we are in a constructor (where inlining object initializers might be critical
                        // for the base ctor call) or a compiler-generated delegate method, which might be used in a query expression.
                        return(false);
                    }
                    // Do not try to transform display class usages or delegate construction.
                    // DelegateConstruction transform cannot deal with this.
                    if (DelegateConstruction.IsSimpleDisplayClass(newObjInst.Method.DeclaringType))
                    {
                        return(false);
                    }
                    if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst))
                    {
                        return(false);
                    }
                    instType = newObjInst.Method.DeclaringType;
                    break;

                case DefaultValue defaultVal:
                    if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor)
                    {
                        // on statement level (no other expressions on IL stack),
                        // prefer to keep local variables (but not stack slots),
                        // unless we are in a constructor (where inlining object initializers might be critical
                        // for the base ctor call)
                        return(false);
                    }
                    instType = defaultVal.Type;
                    break;

                default:
                    return(false);
                }
                int initializerItemsCount = 0;
                var blockKind             = BlockKind.CollectionInitializer;
                possibleIndexVariables = new Dictionary <ILVariable, (int Index, ILInstruction Value)>();
                currentPath            = new List <AccessPathElement>();
                isCollection           = false;
                pathStack = new Stack <HashSet <AccessPathElement> >();
                pathStack.Push(new HashSet <AccessPathElement>());
                // Detect initializer type by scanning the following statements
                // each must be a callvirt with ldloc v as first argument
                // if the method is a setter we're dealing with an object initializer
                // if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
                while (pos + initializerItemsCount + 1 < body.Instructions.Count &&
                       IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind))
                {
                    initializerItemsCount++;
                }
                // Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
                // directly after the possible initializer.
                if (IsMethodCallOnVariable(body.Instructions[pos + initializerItemsCount + 1], v))
                {
                    return(false);
                }
                // Calculate the correct number of statements inside the initializer:
                // All index variables that were used in the initializer have Index set to -1.
                // We fetch the first unused variable from the list and remove all instructions after its
                // first usage (i.e. the init store) from the initializer.
                var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index);
                if (index != null)
                {
                    initializerItemsCount = index.Value - pos - 1;
                }
                // The initializer would be empty, there's nothing to do here.
                if (initializerItemsCount <= 0)
                {
                    return(false);
                }
                context.Step("CollectionOrObjectInitializer", inst);
                // Create a new block and final slot (initializer target variable)
                var        initializerBlock = new Block(blockKind);
                ILVariable finalSlot        = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                initializerBlock.FinalInstruction = new LdLoc(finalSlot);
                initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone()));
                // Move all instructions to the initializer block.
                for (int i = 1; i <= initializerItemsCount; i++)
                {
                    switch (body.Instructions[i + pos])
                    {
                    case CallInstruction call:
                        if (!(call is CallVirt || call is Call))
                        {
                            continue;
                        }
                        var newCall   = call;
                        var newTarget = newCall.Arguments[0];
                        foreach (var load in newTarget.Descendants.OfType <IInstructionWithVariableOperand>())
                        {
                            if ((load is LdLoc || load is LdLoca) && load.Variable == v)
                            {
                                load.Variable = finalSlot;
                            }
                        }
                        initializerBlock.Instructions.Add(newCall);
                        break;

                    case StObj stObj:
                        var newStObj = stObj;
                        foreach (var load in newStObj.Target.Descendants.OfType <IInstructionWithVariableOperand>())
                        {
                            if ((load is LdLoc || load is LdLoca) && load.Variable == v)
                            {
                                load.Variable = finalSlot;
                            }
                        }
                        initializerBlock.Instructions.Add(newStObj);
                        break;

                    case StLoc stLoc:
                        var newStLoc = stLoc;
                        initializerBlock.Instructions.Add(newStLoc);
                        break;
                    }
                }
                initInst.ReplaceWith(initializerBlock);
                body.Instructions.RemoveRange(pos + 1, initializerItemsCount);
                ILInlining.InlineIfPossible(body, pos, context);
            }
            return(true);
        }
Пример #12
0
        protected internal override void VisitComp(Comp inst)
        {
            // "logic.not(arg)" is sugar for "comp(arg != ldc.i4 0)"
            if (inst.MatchLogicNot(out var arg))
            {
                VisitLogicNot(inst, arg);
                return;
            }
            else if (inst.Kind == ComparisonKind.Inequality && inst.LiftingKind == ComparisonLiftingKind.None &&
                     inst.Right.MatchLdcI4(0) && (IfInstruction.IsInConditionSlot(inst) || inst.Left is Comp)
                     )
            {
                // if (comp(x != 0)) ==> if (x)
                // comp(comp(...) != 0) => comp(...)
                context.Step("Remove redundant comp(... != 0)", inst);
                inst.Left.AddILRange(inst);
                inst.ReplaceWith(inst.Left);
                inst.Left.AcceptVisitor(this);
                return;
            }

            base.VisitComp(inst);
            if (inst.IsLifted)
            {
                return;
            }
            if (inst.Right.MatchLdNull())
            {
                if (inst.Kind == ComparisonKind.GreaterThan)
                {
                    context.Step("comp(left > ldnull)  => comp(left != ldnull)", inst);
                    inst.Kind = ComparisonKind.Inequality;
                }
                else if (inst.Kind == ComparisonKind.LessThanOrEqual)
                {
                    context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst);
                    inst.Kind = ComparisonKind.Equality;
                }
            }
            else if (inst.Left.MatchLdNull())
            {
                if (inst.Kind == ComparisonKind.LessThan)
                {
                    context.Step("comp(ldnull < right)  => comp(ldnull != right)", inst);
                    inst.Kind = ComparisonKind.Inequality;
                }
                else if (inst.Kind == ComparisonKind.GreaterThanOrEqual)
                {
                    context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst);
                    inst.Kind = ComparisonKind.Equality;
                }
            }

            var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend);

            if (rightWithoutConv.MatchLdcI4(0) &&
                inst.Sign == Sign.Unsigned &&
                (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual))
            {
                if (inst.Kind == ComparisonKind.GreaterThan)
                {
                    context.Step("comp.unsigned(left > ldc.i4 0) => comp(left != ldc.i4 0)", inst);
                    inst.Kind = ComparisonKind.Inequality;
                    VisitComp(inst);
                    return;
                }
                else if (inst.Kind == ComparisonKind.LessThanOrEqual)
                {
                    context.Step("comp.unsigned(left <= ldc.i4 0) => comp(left == ldc.i4 0)", inst);
                    inst.Kind = ComparisonKind.Equality;
                    VisitComp(inst);
                    return;
                }
            }
            else if (rightWithoutConv.MatchLdcI4(0) && inst.Kind.IsEqualityOrInequality())
            {
                if (inst.Left.MatchLdLen(StackType.I, out ILInstruction array))
                {
                    // comp.unsigned(ldlen array == conv i4->i(ldc.i4 0))
                    // => comp(ldlen.i4 array == ldc.i4 0)
                    // This is a special case where the C# compiler doesn't generate conv.i4 after ldlen.
                    context.Step("comp(ldlen.i4 array == ldc.i4 0)", inst);
                    inst.InputType = StackType.I4;
                    inst.Left.ReplaceWith(new LdLen(StackType.I4, array).WithILRange(inst.Left));
                    inst.Right = rightWithoutConv;
                }
                else if (inst.Left is Conv conv && conv.TargetType == PrimitiveType.I && conv.Argument.ResultType == StackType.O)
                {
                    // C++/CLI sometimes uses this weird comparison with null:
                    context.Step("comp(conv o->i (ldloc obj) == conv i4->i <sign extend>(ldc.i4 0))", inst);
                    // -> comp(ldloc obj == ldnull)
                    inst.InputType = StackType.O;
                    inst.Left      = conv.Argument;
                    inst.Right     = new LdNull().WithILRange(inst.Right);
                    inst.Right.AddILRange(rightWithoutConv);
                }
            }

            if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out arg, out var type) && type.Kind == TypeKind.TypeParameter)
            {
                if (inst.Kind == ComparisonKind.Equality)
                {
                    context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst);
                    inst.Left = arg;
                }
                if (inst.Kind == ComparisonKind.Inequality)
                {
                    context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst);
                    inst.Left = arg;
                }
            }
        }
Пример #13
0
        bool DoTransform(Block body, int pos)
        {
            ILInstruction inst = body.Instructions[pos];

            // Match stloc(v, newobj)
            if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot))
            {
                Block initializerBlock = null;
                IType instType;
                switch (initInst)
                {
                case NewObj newObjInst:
                    if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor)
                    {
                        // on statement level (no other expressions on IL stack),
                        // prefer to keep local variables (but not stack slots),
                        // unless we are in a constructor (where inlining object initializers might be critical
                        // for the base ctor call)
                        return(false);
                    }
                    // Do not try to transform display class usages or delegate construction.
                    // DelegateConstruction transform cannot deal with this.
                    if (DelegateConstruction.IsSimpleDisplayClass(newObjInst.Method.DeclaringType))
                    {
                        return(false);
                    }
                    if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst))
                    {
                        return(false);
                    }
                    instType = newObjInst.Method.DeclaringType;
                    break;

                case DefaultValue defaultVal:
                    instType = defaultVal.Type;
                    break;

                case Block existingInitializer:
                    if (existingInitializer.Type == BlockType.CollectionInitializer || existingInitializer.Type == BlockType.ObjectInitializer)
                    {
                        initializerBlock = existingInitializer;
                        var value = ((StLoc)initializerBlock.Instructions[0]).Value;
                        if (value is NewObj no)
                        {
                            instType = no.Method.DeclaringType;
                        }
                        else
                        {
                            instType = ((DefaultValue)value).Type;
                        }
                        break;
                    }
                    return(false);

                default:
                    return(false);
                }
                int initializerItemsCount  = 0;
                var blockType              = initializerBlock?.Type ?? BlockType.CollectionInitializer;
                var possibleIndexVariables = new Dictionary <ILVariable, (int Index, ILInstruction Value)>();
                // Detect initializer type by scanning the following statements
                // each must be a callvirt with ldloc v as first argument
                // if the method is a setter we're dealing with an object initializer
                // if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
                while (pos + initializerItemsCount + 1 < body.Instructions.Count &&
                       IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockType, possibleIndexVariables))
                {
                    initializerItemsCount++;
                }
                var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index);
                if (index != null)
                {
                    initializerItemsCount = index.Value - pos - 1;
                }
                if (initializerItemsCount <= 0)
                {
                    return(false);
                }
                context.Step("CollectionOrObjectInitializer", inst);
                ILVariable finalSlot;
                if (initializerBlock == null)
                {
                    initializerBlock = new Block(blockType);
                    finalSlot        = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                    initializerBlock.FinalInstruction = new LdLoc(finalSlot);
                    initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone()));
                }
                else
                {
                    finalSlot = ((LdLoc)initializerBlock.FinalInstruction).Variable;
                }
                for (int i = 1; i <= initializerItemsCount; i++)
                {
                    switch (body.Instructions[i + pos])
                    {
                    case CallInstruction call:
                        if (!(call is CallVirt || call is Call))
                        {
                            continue;
                        }
                        var newCall   = (CallInstruction)call.Clone();
                        var newTarget = newCall.Arguments[0];
                        foreach (var load in newTarget.Descendants.OfType <IInstructionWithVariableOperand>())
                        {
                            if ((load is LdLoc || load is LdLoca) && load.Variable == v)
                            {
                                load.Variable = finalSlot;
                            }
                        }
                        initializerBlock.Instructions.Add(newCall);
                        break;

                    case StObj stObj:
                        var newStObj = (StObj)stObj.Clone();
                        foreach (var load in newStObj.Target.Descendants.OfType <IInstructionWithVariableOperand>())
                        {
                            if ((load is LdLoc || load is LdLoca) && load.Variable == v)
                            {
                                load.Variable = finalSlot;
                            }
                        }
                        initializerBlock.Instructions.Add(newStObj);
                        break;

                    case StLoc stLoc:
                        var newStLoc = (StLoc)stLoc.Clone();
                        initializerBlock.Instructions.Add(newStLoc);
                        break;
                    }
                }
                initInst.ReplaceWith(initializerBlock);
                body.Instructions.RemoveRange(pos + 1, initializerItemsCount);
                ILInlining.InlineIfPossible(body, pos, context);
            }
            return(true);
        }
        bool DoTransform(Block body, int pos)
        {
            if (pos >= body.Instructions.Count - 2)
            {
                return(false);
            }
            ILInstruction inst = body.Instructions[pos];
            ILVariable    v;
            ILInstruction newarrExpr;
            IType         elementType;

            int[] arrayLength;
            if (inst.MatchStLoc(out v, out newarrExpr) && MatchNewArr(newarrExpr, out elementType, out arrayLength))
            {
                ILInstruction[] values;
                int             initArrayPos;
                if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out values, out initArrayPos))
                {
                    context.Step("ForwardScanInitializeArrayRuntimeHelper", inst);
                    var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                    var block     = BlockFromInitializer(tempStore, elementType, arrayLength, values);
                    body.Instructions[pos] = new StLoc(v, block);
                    body.Instructions.RemoveAt(initArrayPos);
                    ILInlining.InlineIfPossible(body, pos, context);
                    return(true);
                }
                if (arrayLength.Length == 1)
                {
                    int instructionsToRemove;
                    if (HandleSimpleArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out values, out instructionsToRemove))
                    {
                        context.Step("HandleSimpleArrayInitializer", inst);
                        var block     = new Block(BlockKind.ArrayInitializer);
                        var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                        block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
                        block.Instructions.AddRange(values.SelectWithIndex(
                                                        (i, value) => {
                            if (value == null)
                            {
                                value = GetNullExpression(elementType);
                            }
                            return(StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType));
                        }
                                                        ));
                        block.FinalInstruction = new LdLoc(tempStore);
                        body.Instructions[pos] = new StLoc(v, block);
                        body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
                        ILInlining.InlineIfPossible(body, pos, context);
                        return(true);
                    }
                    if (HandleJaggedArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out ILVariable finalStore, out values, out instructionsToRemove))
                    {
                        context.Step("HandleJaggedArrayInitializer", inst);
                        var block     = new Block(BlockKind.ArrayInitializer);
                        var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                        block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
                        block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType)));
                        block.FinalInstruction = new LdLoc(tempStore);
                        body.Instructions[pos] = new StLoc(finalStore, block);
                        body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
                        ILInlining.InlineIfPossible(body, pos, context);
                        return(true);
                    }
                }
                // Put in a limit so that we don't consume too much memory if the code allocates a huge array
                // and populates it extremely sparsly. However, 255 "null" elements in a row actually occur in the Mono C# compiler!
//				const int maxConsecutiveDefaultValueExpressions = 300;
//				var operands = new List<ILInstruction>();
//				int numberOfInstructionsToRemove = 0;
//				for (int j = pos + 1; j < body.Instructions.Count; j++) {
//					var nextExpr = body.Instructions[j] as Void;
//					int arrayPos;
//					if (nextExpr != null && nextExpr is a.IsStoreToArray() &&
//					    nextExpr.Arguments[0].Match(ILCode.Ldloc, out v3) &&
//					    v == v3 &&
//					    nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) &&
//					    arrayPos >= operands.Count &&
//					    arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions &&
//					    !nextExpr.Arguments[2].ContainsReferenceTo(v3))
//					{
//						while (operands.Count < arrayPos)
//							operands.Add(new ILExpression(ILCode.DefaultValue, elementType));
//						operands.Add(nextExpr.Arguments[2]);
//						numberOfInstructionsToRemove++;
//					} else {
//						break;
//					}
//				}
            }
            return(false);
        }
Пример #15
0
        /// <code>
        /// stloc s(value)
        /// stloc l(ldloc s)
        /// stobj(..., ldloc s)
        ///   where ... is pure and does not use s or l,
        ///   and where neither the 'stloc s' nor the 'stobj' truncates
        /// -->
        /// stloc l(stobj (..., value))
        /// </code>
        /// e.g. used for inline assignment to instance field
        ///
        /// -or-
        ///
        /// <code>
        /// stloc s(value)
        /// stobj (..., ldloc s)
        ///   where ... is pure and does not use s, and where the 'stobj' does not truncate
        /// -->
        /// stloc s(stobj (..., value))
        /// </code>
        /// e.g. used for inline assignment to static field
        ///
        /// -or-
        ///
        /// <code>
        /// stloc s(value)
        /// call set_Property(..., ldloc s)
        ///   where the '...' arguments are pure and not using 's'
        /// -->
        /// stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i })
        ///   new temporary 'i' has type of the property; transform only valid if 'stloc i' doesn't truncate
        /// </code>
        bool TransformInlineAssignmentStObjOrCall(Block block, int pos)
        {
            var inst = block.Instructions[pos] as StLoc;

            // in some cases it can be a compiler-generated local
            if (inst == null || (inst.Variable.Kind != VariableKind.StackSlot && inst.Variable.Kind != VariableKind.Local))
            {
                return(false);
            }
            if (IsImplicitTruncation(inst.Value, inst.Variable.Type, context.TypeSystem))
            {
                // 'stloc s' is implicitly truncating the value
                return(false);
            }
            ILVariable local;
            int        nextPos;

            if (block.Instructions[pos + 1] is StLoc localStore)               // with extra local
            {
                if (localStore.Variable.Kind != VariableKind.Local || !localStore.Value.MatchLdLoc(inst.Variable))
                {
                    return(false);
                }
                // if we're using an extra local, we'll delete "s", so check that that doesn't have any additional uses
                if (!(inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 2))
                {
                    return(false);
                }
                local   = localStore.Variable;
                nextPos = pos + 2;
            }
            else
            {
                local      = inst.Variable;
                localStore = null;
                nextPos    = pos + 1;
            }
            if (block.Instructions[nextPos] is StObj stobj)
            {
                if (!stobj.Value.MatchLdLoc(inst.Variable))
                {
                    return(false);
                }
                if (!SemanticHelper.IsPure(stobj.Target.Flags) || inst.Variable.IsUsedWithin(stobj.Target))
                {
                    return(false);
                }
                var   pointerType = stobj.Target.InferType(context.TypeSystem);
                IType newType     = stobj.Type;
                if (TypeUtils.IsCompatiblePointerTypeForMemoryAccess(pointerType, stobj.Type))
                {
                    if (pointerType is ByReferenceType byref)
                    {
                        newType = byref.ElementType;
                    }
                    else if (pointerType is PointerType pointer)
                    {
                        newType = pointer.ElementType;
                    }
                }
                if (IsImplicitTruncation(inst.Value, newType, context.TypeSystem))
                {
                    // 'stobj' is implicitly truncating the value
                    return(false);
                }
                context.Step("Inline assignment stobj", stobj);
                stobj.Type = newType;
                block.Instructions.Remove(localStore);
                block.Instructions.Remove(stobj);
                stobj.Value = inst.Value;
                inst.ReplaceWith(new StLoc(local, stobj));
                // note: our caller will trigger a re-run, which will call HandleStObjCompoundAssign if applicable
                return(true);
            }
            else if (block.Instructions[nextPos] is CallInstruction call)
            {
                // call must be a setter call:
                if (!(call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt))
                {
                    return(false);
                }
                if (call.ResultType != StackType.Void || call.Arguments.Count == 0)
                {
                    return(false);
                }
                IProperty property = call.Method.AccessorOwner as IProperty;
                if (property == null)
                {
                    return(false);
                }
                if (!call.Method.Equals(property.Setter))
                {
                    return(false);
                }
                if (!(property.IsIndexer || property.Setter.Parameters.Count == 1))
                {
                    // this is a parameterized property, which cannot be expressed as C# code.
                    // setter calls are not valid in expression context, if property syntax cannot be used.
                    return(false);
                }
                if (!call.Arguments.Last().MatchLdLoc(inst.Variable))
                {
                    return(false);
                }
                foreach (var arg in call.Arguments.SkipLast(1))
                {
                    if (!SemanticHelper.IsPure(arg.Flags) || inst.Variable.IsUsedWithin(arg))
                    {
                        return(false);
                    }
                }
                if (IsImplicitTruncation(inst.Value, call.Method.Parameters.Last().Type, context.TypeSystem))
                {
                    // setter call is implicitly truncating the value
                    return(false);
                }
                // stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i })
                context.Step("Inline assignment call", call);
                block.Instructions.Remove(localStore);
                block.Instructions.Remove(call);
                var newVar = context.Function.RegisterVariable(VariableKind.StackSlot, call.Method.Parameters.Last().Type);
                call.Arguments[call.Arguments.Count - 1] = new StLoc(newVar, inst.Value);
                var inlineBlock = new Block(BlockKind.CallInlineAssign)
                {
                    Instructions     = { call },
                    FinalInstruction = new LdLoc(newVar)
                };
                inst.ReplaceWith(new StLoc(local, inlineBlock));
                // because the ExpressionTransforms don't look into inline blocks, manually trigger HandleCallCompoundAssign
                if (HandleCompoundAssign(call, context))
                {
                    // if we did construct a compound assignment, it should have made our inline block redundant:
                    Debug.Assert(!inlineBlock.IsConnected);
                }
                return(true);
            }
            else
            {
                return(false);
            }
        }
Пример #16
0
        /// <summary>
        /// Transform compound assignments where the return value is not being used,
        /// or where there's an inlined assignment within the setter call.
        ///
        /// Patterns handled:
        /// 1.
        ///   callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value))
        ///   ==> compound.op.new(callvirt get_Property(ldloc S_1), value)
        /// 2.
        ///   callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
        ///   ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value))
        /// 3.
        ///   stobj(target, binary.op(ldobj(target), ...))
        ///     where target is pure
        ///   => compound.op(target, ...)
        /// </summary>
        /// <remarks>
        /// Called by ExpressionTransforms, or after the inline-assignment transform for setters.
        /// </remarks>
        internal static bool HandleCompoundAssign(ILInstruction compoundStore, StatementTransformContext context)
        {
            if (!context.Settings.MakeAssignmentExpressions || !context.Settings.IntroduceIncrementAndDecrement)
            {
                return(false);
            }
            if (compoundStore is CallInstruction && compoundStore.SlotInfo != Block.InstructionSlot)
            {
                // replacing 'call set_Property' with a compound assignment instruction
                // changes the return value of the expression, so this is only valid on block-level.
                return(false);
            }
            if (!IsCompoundStore(compoundStore, out var targetType, out var setterValue, context.TypeSystem))
            {
                return(false);
            }
            // targetType = The type of the property/field/etc. being stored to.
            // setterValue = The value being stored.
            var storeInSetter = setterValue as StLoc;

            if (storeInSetter != null)
            {
                // We'll move the stloc to top-level:
                // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
                // ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value))
                setterValue = storeInSetter.Value;
                if (storeInSetter.Variable.Type.IsSmallIntegerType())
                {
                    // 'stloc v' implicitly truncates the value.
                    // Ensure that type of 'v' matches the type of the property:
                    if (storeInSetter.Variable.Type.GetSize() != targetType.GetSize())
                    {
                        return(false);
                    }
                    if (storeInSetter.Variable.Type.GetSign() != targetType.GetSign())
                    {
                        return(false);
                    }
                }
            }
            ILInstruction newInst;

            if (UnwrapSmallIntegerConv(setterValue, out var smallIntConv) is BinaryNumericInstruction binary)
            {
                if (compoundStore is StLoc)
                {
                    // transform local variables only for user-defined operators
                    return(false);
                }
                if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable))
                {
                    return(false);
                }
                if (!ValidateCompoundAssign(binary, smallIntConv, targetType))
                {
                    return(false);
                }
                context.Step($"Compound assignment (binary.numeric)", compoundStore);
                finalizeMatch?.Invoke(context);
                newInst = new NumericCompoundAssign(
                    binary, target, targetKind, binary.Right,
                    targetType, CompoundEvalMode.EvaluatesToNewValue);
            }
            else if (setterValue is Call operatorCall && operatorCall.Method.IsOperator)
            {
                if (operatorCall.Arguments.Count == 0)
                {
                    return(false);
                }
                if (!IsMatchingCompoundLoad(operatorCall.Arguments[0], compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable))
                {
                    return(false);
                }
                ILInstruction rhs;
                if (operatorCall.Arguments.Count == 2)
                {
                    if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name) == null)
                    {
                        return(false);
                    }
                    rhs = operatorCall.Arguments[1];
                }
                else if (operatorCall.Arguments.Count == 1)
                {
                    if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement"))
                    {
                        return(false);
                    }
                    // use a dummy node so that we don't need a dedicated instruction for user-defined unary operator calls
                    rhs = new LdcI4(1);
                }
                else
                {
                    return(false);
                }
                if (operatorCall.IsLifted)
                {
                    return(false);                    // TODO: add tests and think about whether nullables need special considerations
                }
                context.Step($"Compound assignment (user-defined binary)", compoundStore);
                finalizeMatch?.Invoke(context);
                newInst = new UserDefinedCompoundAssign(operatorCall.Method, CompoundEvalMode.EvaluatesToNewValue,
                                                        target, targetKind, rhs);
            }
Пример #17
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);
            }
        }