Beispiel #1
0
        private void RemoveDeadStackStores(Block block, bool aggressive)
        {
            // Previously copy propagation did this;
            // ideally the ILReader would already do this,
            // for now do this here (even though it's not control-flow related).
            for (int i = block.Instructions.Count - 1; i >= 0; i--)
            {
                if (block.Instructions[i] is StLoc stloc && stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 0 && stloc.Variable.Kind == VariableKind.StackSlot)
                {
                    if (aggressive ? SemanticHelper.IsPure(stloc.Value.Flags) : IsSimple(stloc.Value))
                    {
                        Debug.Assert(SemanticHelper.IsPure(stloc.Value.Flags));
                        block.Instructions.RemoveAt(i++);
                    }
                    else
                    {
                        stloc.Value.AddILRange(stloc);
                        stloc.ReplaceWith(stloc.Value);
                    }
                }
            }

            bool IsSimple(ILInstruction inst)
            {
                switch (inst.OpCode)
                {
                case OpCode.LdLoc:
                case OpCode.LdStr:                         // C# 1.0 compiler sometimes emits redundant ldstr in switch-on-string pattern
                    return(true);

                default:
                    return(false);
                }
            }
        }
Beispiel #2
0
 public void Run(Block block, BlockTransformContext context)
 {
     for (int i = 0; i < block.Instructions.Count; i++)
     {
         ILVariable    v;
         ILInstruction copiedExpr;
         if (block.Instructions[i].MatchStLoc(out v, out copiedExpr))
         {
             if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot)
             {
                 // dead store to stack
                 if (SemanticHelper.IsPure(copiedExpr.Flags))
                 {
                     // no-op -> delete
                     context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]);
                     block.Instructions.RemoveAt(i--);
                 }
                 else
                 {
                     // evaluate the value for its side-effects
                     context.Step("remove dead store to stack: evaluate the value for its side-effects", block.Instructions[i]);
                     copiedExpr.AddILRange(block.Instructions[i].ILRange);
                     block.Instructions[i] = copiedExpr;
                 }
             }
             else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr))
             {
                 DoPropagate(v, copiedExpr, block, ref i, context);
             }
         }
     }
 }
Beispiel #3
0
 /// <summary>
 /// Lift a C# comparison.
 ///
 /// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c>.
 /// Otherwise, the output instruction should evaluate to the same value as the input instruction.
 /// The output instruction should have the same side-effects (incl. exceptions being thrown) as the input instruction.
 /// This means unlike LiftNormal(), we cannot rely on the input instruction not being evaluated if
 /// a variable is <c>null</c>.
 /// </summary>
 Comp LiftCSharpComparison(Comp comp, ComparisonKind newComparisonKind)
 {
     var(left, leftBits)   = DoLift(comp.Left);
     var(right, rightBits) = DoLift(comp.Right);
     if (left != null && right == null && SemanticHelper.IsPure(comp.Right.Flags))
     {
         // Embed non-nullable pure expression in lifted expression.
         right = comp.Right.Clone();
     }
     if (left == null && right != null && SemanticHelper.IsPure(comp.Left.Flags))
     {
         // Embed non-nullable pure expression in lifted expression.
         left = comp.Left.Clone();
     }
     // due to the restrictions on side effects, we only allow instructions that are pure after lifting.
     // (we can't check this before lifting due to the calls to GetValueOrDefault())
     if (left != null && right != null && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags))
     {
         var bits = leftBits ?? rightBits;
         if (rightBits != null)
         {
             bits.UnionWith(rightBits);
         }
         if (!bits.All(0, nullableVars.Count))
         {
             // don't lift if a nullableVar doesn't contribute to the result
             return(null);
         }
         context.Step("NullableLiftingTransform: C# comparison", comp);
         return(new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, comp.InputType, comp.Sign, left, right));
     }
     return(null);
 }
        /// <summary>
        /// Unwraps a nested BlockContainer, if container contains only a single block,
        /// and that single block contains only a BlockContainer followed by a Leave instruction.
        /// If the leave instruction is a return that carries a value, the container is unwrapped only
        /// if the value has no side-effects.
        /// Otherwise returns the unmodified container.
        /// </summary>
        /// <param name="optionalReturnInst">If the leave is a return and has no side-effects, we can move the return out of the using-block and put it after the loop, otherwise returns null.</param>
        BlockContainer UnwrapNestedContainerIfPossible(BlockContainer container, out Leave optionalReturnInst)
        {
            optionalReturnInst = null;
            // Check block structure:
            if (container.Blocks.Count != 1)
            {
                return(container);
            }
            var nestedBlock = container.Blocks[0];

            if (nestedBlock.Instructions.Count != 2 ||
                !(nestedBlock.Instructions[0] is BlockContainer nestedContainer) ||
                !(nestedBlock.Instructions[1] is Leave leave))
            {
                return(container);
            }
            // If the leave has no value, just unwrap the BlockContainer.
            if (leave.MatchLeave(container))
            {
                return(nestedContainer);
            }
            // If the leave is a return, we can move the return out of the using-block and put it after the loop
            // (but only if the value doesn't have side-effects)
            if (leave.IsLeavingFunction && SemanticHelper.IsPure(leave.Value.Flags))
            {
                optionalReturnInst = leave;
                return(nestedContainer);
            }
            return(container);
        }
Beispiel #5
0
 /// <summary>
 /// Gets whether 'inst' is a possible store for use as a compound store.
 /// </summary>
 bool IsCompoundStore(ILInstruction inst, out IType storeType, out ILInstruction value)
 {
     value     = null;
     storeType = null;
     if (inst is StObj stobj)
     {
         storeType = stobj.Type;
         value     = stobj.Value;
         return(SemanticHelper.IsPure(stobj.Target.Flags));
     }
     else if (inst is CallInstruction call && (call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt))
     {
         if (call.Method.Parameters.Count == 0)
         {
             return(false);
         }
         foreach (var arg in call.Arguments.SkipLast(1))
         {
             if (!SemanticHelper.IsPure(arg.Flags))
             {
                 return(false);
             }
         }
         storeType = call.Method.Parameters.Last().Type;
         value     = call.Arguments.Last();
         return(IsSameMember(call.Method, (call.Method.AccessorOwner as IProperty)?.Setter));
     }
Beispiel #6
0
 /// <summary>
 /// newobj Delegate..ctor(target, ldvirtftn TargetMethod(target))
 /// =>
 /// ldvirtdelegate System.Delegate TargetMethod(target)
 /// </summary>
 bool TransformDelegateCtorLdVirtFtnToLdVirtDelegate(NewObj inst, out LdVirtDelegate ldVirtDelegate)
 {
     ldVirtDelegate = null;
     if (inst.Method.DeclaringType.Kind != TypeKind.Delegate)
     {
         return(false);
     }
     if (inst.Arguments.Count != 2)
     {
         return(false);
     }
     if (!(inst.Arguments[1] is LdVirtFtn ldVirtFtn))
     {
         return(false);
     }
     if (!SemanticHelper.IsPure(inst.Arguments[0].Flags))
     {
         return(false);
     }
     if (!inst.Arguments[0].Match(ldVirtFtn.Argument).Success)
     {
         return(false);
     }
     ldVirtDelegate = new LdVirtDelegate(inst.Arguments[0], inst.Method.DeclaringType, ldVirtFtn.Method)
                      .WithILRange(inst).WithILRange(ldVirtFtn).WithILRange(ldVirtFtn.Argument);
     return(true);
 }
Beispiel #7
0
        static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall)
        {
            if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner))
            {
                return(false);
            }
            var owner = getterCall.Method.AccessorOwner as IProperty;

            if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter))
            {
                return(false);
            }
            if (setterCall.Arguments.Count != getterCall.Arguments.Count + 1)
            {
                return(false);
            }
            // Ensure that same arguments are passed to getterCall and setterCall:
            for (int j = 0; j < getterCall.Arguments.Count; j++)
            {
                if (!SemanticHelper.IsPure(getterCall.Arguments[j].Flags))
                {
                    return(false);
                }
                if (!getterCall.Arguments[j].Match(setterCall.Arguments[j]).Success)
                {
                    return(false);
                }
            }
            return(true);
        }
Beispiel #8
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));
 }
 public void Run(ILFunction function, ILTransformContext context)
 {
     foreach (var container in function.Descendants.OfType <BlockContainer>())
     {
         context.CancellationToken.ThrowIfCancellationRequested();
         SplitBlocksAtWritesToPinnedLocals(container);
         DetectNullSafeArrayToPointer(container);
         foreach (var block in container.Blocks)
         {
             CreatePinnedRegion(block);
         }
         container.Blocks.RemoveAll(b => b.Instructions.Count == 0);                 // remove dummy blocks
     }
     // Sometimes there's leftover writes to the original pinned locals
     foreach (var block in function.Descendants.OfType <Block>())
     {
         context.CancellationToken.ThrowIfCancellationRequested();
         for (int i = 0; i < block.Instructions.Count; i++)
         {
             var stloc = block.Instructions[i] as StLoc;
             if (stloc != null && stloc.Variable.Kind == VariableKind.PinnedLocal && stloc.Variable.LoadCount == 0 && stloc.Variable.AddressCount == 0)
             {
                 if (SemanticHelper.IsPure(stloc.Value.Flags))
                 {
                     block.Instructions.RemoveAt(i--);
                 }
                 else
                 {
                     stloc.ReplaceWith(stloc.Value);
                 }
             }
         }
     }
 }
Beispiel #10
0
        protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst)
        {
            base.VisitBinaryNumericInstruction(inst);
            switch (inst.Operator)
            {
            case BinaryNumericOperator.ShiftLeft:
            case BinaryNumericOperator.ShiftRight:
                if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs) &&
                    rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31))
                {
                    // a << (b & 31) => a << b
                    context.Step("Combine bit.and into shift", inst);
                    inst.Right = lhs;
                }
                break;

            case BinaryNumericOperator.BitAnd:
                if (inst.Left.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean) &&
                    inst.Right.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean))
                {
                    if (new NullableLiftingTransform(context).Run(inst))
                    {
                        // e.g. "(a.GetValueOrDefault() == b.GetValueOrDefault()) & (a.HasValue & b.HasValue)"
                    }
                    else if (SemanticHelper.IsPure(inst.Right.Flags))
                    {
                        context.Step("Replace bit.and with logic.and", inst);
                        var expr = IfInstruction.LogicAnd(inst.Left, inst.Right);
                        inst.ReplaceWith(expr);
                        expr.AcceptVisitor(this);
                    }
                }
                break;
            }
        }
Beispiel #11
0
 static void RunOnBlock(Block block, ILTransformContext context, HashSet <ILVariable> splitVariables = null)
 {
     for (int i = 0; i < block.Instructions.Count; i++)
     {
         if (block.Instructions[i].MatchStLoc(out ILVariable v, out ILInstruction copiedExpr))
         {
             if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot)
             {
                 // dead store to stack
                 if (SemanticHelper.IsPure(copiedExpr.Flags))
                 {
                     // no-op -> delete
                     context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]);
                     block.Instructions.RemoveAt(i);
                     // This can open up new inlining opportunities:
                     int c = ILInlining.InlineInto(block, i, InliningOptions.None, context: context);
                     i -= c + 1;
                 }
                 else
                 {
                     // evaluate the value for its side-effects
                     context.Step("remove dead store to stack: evaluate the value for its side-effects", block.Instructions[i]);
                     copiedExpr.AddILRange(block.Instructions[i]);
                     block.Instructions[i] = copiedExpr;
                 }
             }
             else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr, splitVariables))
             {
                 DoPropagate(v, copiedExpr, block, ref i, context);
             }
         }
     }
 }
Beispiel #12
0
        void IILTransform.Run(ILFunction function, ILTransformContext context)
        {
            var instructionsToFix = new List <IsInst>();

            foreach (var isInst in function.Descendants.OfType <IsInst>())
            {
                if (isInst.Type.IsReferenceType == true)
                {
                    continue;                      // reference-type isinst is always supported
                }
                if (SemanticHelper.IsPure(isInst.Argument.Flags))
                {
                    continue;                     // emulated via "expr is T ? (T)expr : null"
                }
                if (isInst.Parent is UnboxAny unboxAny && ExpressionBuilder.IsUnboxAnyWithIsInst(unboxAny, isInst))
                {
                    continue;                     // supported pattern "expr as T?"
                }
                if (isInst.Parent.MatchCompEqualsNull(out _) || isInst.Parent.MatchCompNotEqualsNull(out _))
                {
                    continue;                     // supported pattern "expr is T"
                }
                if (isInst.Parent is Block {
                    Kind: BlockKind.ControlFlow
                })
Beispiel #13
0
        protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst)
        {
            base.VisitBinaryNumericInstruction(inst);
            switch (inst.Operator)
            {
            case BinaryNumericOperator.ShiftLeft:
            case BinaryNumericOperator.ShiftRight:
                if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs) &&
                    rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31))
                {
                    // a << (b & 31) => a << b
                    context.Step("Combine bit.and into shift", inst);
                    inst.Right = lhs;
                }
                break;

            case BinaryNumericOperator.BitAnd:
                if (IsBoolean(inst.Left) && IsBoolean(inst.Right) && SemanticHelper.IsPure(inst.Right.Flags))
                {
                    context.Step("Replace bit.and with logic.and", inst);
                    var expr = IfInstruction.LogicAnd(inst.Left, inst.Right);
                    inst.ReplaceWith(expr);
                    expr.AcceptVisitor(this);
                }
                break;
            }
        }
Beispiel #14
0
        Comp LiftCSharpEqualityComparison(Comp valueComp, ComparisonKind newComparisonKind, ILInstruction hasValueTest)
        {
            Debug.Assert(newComparisonKind.IsEqualityOrInequality());
            bool hasValueTestNegated = false;

            while (hasValueTest.MatchLogicNot(out var arg))
            {
                hasValueTest        = arg;
                hasValueTestNegated = !hasValueTestNegated;
            }
            // The HasValue comparison must be the same operator as the Value comparison.
            if (hasValueTest is Comp hasValueComp)
            {
                // Comparing two nullables: HasValue comparison must be the same operator as the Value comparison
                if ((hasValueTestNegated ? hasValueComp.Kind.Negate() : hasValueComp.Kind) != newComparisonKind)
                {
                    return(null);
                }
                if (!MatchHasValueCall(hasValueComp.Left, out var leftVar))
                {
                    return(null);
                }
                if (!MatchHasValueCall(hasValueComp.Right, out var rightVar))
                {
                    return(null);
                }
                nullableVars = new List <ILVariable> {
                    leftVar
                };
                var(left, leftBits)   = DoLift(valueComp.Left);
                nullableVars[0]       = rightVar;
                var(right, rightBits) = DoLift(valueComp.Right);
                if (left != null && right != null && leftBits[0] && rightBits[0] &&
                    SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags)
                    )
                {
                    context.Step("NullableLiftingTransform: C# (in)equality comparison", valueComp);
                    return(new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, valueComp.InputType, valueComp.Sign, left, right));
                }
            }
            else if (newComparisonKind == ComparisonKind.Equality && !hasValueTestNegated && MatchHasValueCall(hasValueTest, out var v))
            {
                // Comparing nullable with non-nullable -> we can fall back to the normal comparison code.
                nullableVars = new List <ILVariable> {
                    v
                };
                return(LiftCSharpComparison(valueComp, newComparisonKind));
            }
            else if (newComparisonKind == ComparisonKind.Inequality && hasValueTestNegated && MatchHasValueCall(hasValueTest, out v))
            {
                // Comparing nullable with non-nullable -> we can fall back to the normal comparison code.
                nullableVars = new List <ILVariable> {
                    v
                };
                return(LiftCSharpComparison(valueComp, newComparisonKind));
            }
            return(null);
        }
        public void Run(ILFunction function, ILTransformContext context)
        {
            var visitor = new DefiniteAssignmentVisitor(function, context.CancellationToken);

            function.Body.AcceptVisitor(visitor);
            foreach (var v in function.Variables)
            {
                if (v.Kind != VariableKind.Parameter && !visitor.IsPotentiallyUsedUninitialized(v))
                {
                    v.HasInitialValue = false;
                }
            }
            // Remove dead stores to variables that are never read from.
            // If the stored value has some side-effect, the value is unwrapped.
            // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler.
            // In yield return + async, the C# compiler tends to store null/default(T) to variables
            // when the variable goes out of scope.
            if (function.IsAsync || function.IsIterator || context.Settings.RemoveDeadCode)
            {
                var variableQueue = new Queue <ILVariable>(function.Variables);
                while (variableQueue.Count > 0)
                {
                    var v = variableQueue.Dequeue();
                    if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot)
                    {
                        continue;
                    }
                    if (v.LoadCount != 0 || v.AddressCount != 0)
                    {
                        continue;
                    }
                    foreach (var stloc in v.StoreInstructions.OfType <StLoc>().ToArray())
                    {
                        if (stloc.Parent is Block block)
                        {
                            if (SemanticHelper.IsPure(stloc.Value.Flags))
                            {
                                block.Instructions.Remove(stloc);
                            }
                            else
                            {
                                stloc.ReplaceWith(stloc.Value);
                            }
                            if (stloc.Value is LdLoc ldloc)
                            {
                                variableQueue.Enqueue(ldloc.Variable);
                            }
                        }
                    }
                }
            }
        }
Beispiel #16
0
 /// <summary> Drops the result value. The result expression is still executed, though. </summary>
 public StatementBlock AsVoid()
 {
     if (IsVoid)
     {
         return(this);
     }
     else if (SemanticHelper.IsPure(Instruction.Flags))
     {
         return(new StatementBlock(Statements));
     }
     else
     {
         return(new StatementBlock(Statements.Add(new ExpressionStatement(Instr()))));
     }
 }
Beispiel #17
0
 /// <summary>
 /// Lift a C# comparison.
 /// This method cannot be used for (in)equality comparisons where both sides are nullable
 /// (these special cases are handled in LiftCSharpEqualityComparison instead).
 ///
 /// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c>
 ///   (except for newComparisonKind==Inequality, where this case should evaluate to <c>true</c> instead).
 /// Otherwise, the output instruction should evaluate to the same value as the input instruction.
 /// The output instruction should have the same side-effects (incl. exceptions being thrown) as the input instruction.
 /// This means unlike LiftNormal(), we cannot rely on the input instruction not being evaluated if
 /// a variable is <c>null</c>.
 /// </summary>
 Comp LiftCSharpComparison(Comp comp, ComparisonKind newComparisonKind)
 {
     var(left, right, bits) = DoLiftBinary(comp.Left, comp.Right);
     // due to the restrictions on side effects, we only allow instructions that are pure after lifting.
     // (we can't check this before lifting due to the calls to GetValueOrDefault())
     if (left != null && right != null && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags))
     {
         if (!bits.All(0, nullableVars.Count))
         {
             // don't lift if a nullableVar doesn't contribute to the result
             return(null);
         }
         context.Step("NullableLiftingTransform: C# comparison", comp);
         return(new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, comp.InputType, comp.Sign, left, right));
     }
     return(null);
 }
Beispiel #18
0
        /// <summary>
        /// stobj(target, binary.op(ldobj(target), ...))
        ///   where target is pure
        /// => compound.op(target, ...)
        /// </summary>
        /// <remarks>
        /// Called by ExpressionTransforms.
        /// </remarks>
        internal static bool HandleStObjCompoundAssign(StObj inst, ILTransformContext context)
        {
            if (!(UnwrapSmallIntegerConv(inst.Value, out var conv) is BinaryNumericInstruction binary))
            {
                return(false);
            }
            if (!(binary.Left is LdObj ldobj))
            {
                return(false);
            }
            if (!inst.Target.Match(ldobj.Target).Success)
            {
                return(false);
            }
            if (!SemanticHelper.IsPure(ldobj.Target.Flags))
            {
                return(false);
            }
            // ldobj.Type may just be 'int' (due to ldind.i4) when we're actually operating on a 'ref MyEnum'.
            // Try to determine the real type of the object we're modifying:
            IType targetType = ldobj.Target.InferType();

            if (targetType.Kind == TypeKind.Pointer || targetType.Kind == TypeKind.ByReference)
            {
                targetType = ((TypeWithElementType)targetType).ElementType;
                if (targetType.Kind == TypeKind.Unknown || targetType.GetSize() != ldobj.Type.GetSize())
                {
                    targetType = ldobj.Type;
                }
            }
            else
            {
                targetType = ldobj.Type;
            }
            if (!ValidateCompoundAssign(binary, conv, targetType))
            {
                return(false);
            }
            context.Step("compound assignment", inst);
            inst.ReplaceWith(new CompoundAssignmentInstruction(
                                 binary, binary.Left, binary.Right,
                                 targetType, CompoundAssignmentType.EvaluatesToNewValue));
            return(true);
        }
Beispiel #19
0
        /// <summary>
        /// dynamic.setindex.compound(target, index, dynamic.binary.operator op(dynamic.getindex(target, index), value))
        /// =>
        /// dynamic.compound.op (dynamic.getindex(target, index), value)
        /// </summary>
        protected internal override void VisitDynamicSetIndexInstruction(DynamicSetIndexInstruction inst)
        {
            base.VisitDynamicSetIndexInstruction(inst);

            if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment))
            {
                return;
            }
            if (!(inst.Arguments.LastOrDefault() is DynamicBinaryOperatorInstruction binaryOp))
            {
                return;
            }
            if (!(binaryOp.Left is DynamicGetIndexInstruction dynamicGetIndex))
            {
                return;
            }
            if (inst.Arguments.Count != dynamicGetIndex.Arguments.Count + 1)
            {
                return;
            }
            // Ensure that same arguments are passed to dynamicGetIndex and inst:
            for (int j = 0; j < dynamicGetIndex.Arguments.Count; j++)
            {
                if (!SemanticHelper.IsPure(dynamicGetIndex.Arguments[j].Flags))
                {
                    return;
                }
                if (!dynamicGetIndex.Arguments[j].Match(inst.Arguments[j]).Success)
                {
                    return;
                }
            }
            if (!DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation))
            {
                return;
            }
            context.Step("dynamic.setindex.compound -> dynamic.compound.op", inst);
            inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo));
        }
Beispiel #20
0
        /// <summary>
        /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction.
        ///
        /// Note that this method does not check whether 'v' has only one use;
        /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'.
        /// </summary>
        public static bool InlineOne(StLoc stloc, InliningOptions options, ILTransformContext context)
        {
            ILVariable v     = stloc.Variable;
            Block      block = (Block)stloc.Parent;
            int        pos   = stloc.ChildIndex;

            if (DoInline(v, stloc.Value, block.Instructions.ElementAtOrDefault(pos + 1), options, context))
            {
                // Assign the ranges of the stloc instruction:
                stloc.Value.AddILRange(stloc);
                // Remove the stloc instruction:
                Debug.Assert(block.Instructions[pos] == stloc);
                block.Instructions.RemoveAt(pos);
                return(true);
            }
            else if (v.LoadCount == 0 && v.AddressCount == 0)
            {
                // The variable is never loaded
                if (SemanticHelper.IsPure(stloc.Value.Flags))
                {
                    // Remove completely if the instruction has no effects
                    // (except for reading locals)
                    context.Step("Remove dead store without side effects", stloc);
                    block.Instructions.RemoveAt(pos);
                    return(true);
                }
                else if (v.Kind == VariableKind.StackSlot)
                {
                    context.Step("Remove dead store, but keep expression", stloc);
                    // Assign the ranges of the stloc instruction:
                    stloc.Value.AddILRange(stloc);
                    // Remove the stloc, but keep the inner expression
                    stloc.ReplaceWith(stloc.Value);
                    return(true);
                }
            }
            return(false);
        }
Beispiel #21
0
        protected internal override void VisitStObj(StObj inst)
        {
            base.VisitStObj(inst);
            if (EarlyExpressionTransforms.StObjToStLoc(inst, context))
            {
                context.RequestRerun();
                return;
            }

            if (inst.Value is BinaryNumericInstruction binary &&
                binary.Left.MatchLdObj(out ILInstruction target, out IType t) &&
                inst.Target.Match(target).Success &&
                SemanticHelper.IsPure(target.Flags) &&
                CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, t))
            {
                context.Step("compound assignment", inst);
                // stobj(target, binary.op(ldobj(target), ...))
                // => compound.op(target, ...)
                inst.ReplaceWith(new CompoundAssignmentInstruction(
                                     binary, binary.Left, binary.Right,
                                     t, CompoundAssignmentType.EvaluatesToNewValue));
            }
        }
Beispiel #22
0
        static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall, out Action <ILTransformContext> finalizeMatch)
        {
            finalizeMatch = null;
            if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner))
            {
                return(false);
            }
            if (setterCall.OpCode != getterCall.OpCode)
            {
                return(false);
            }
            var owner = getterCall.Method.AccessorOwner as IProperty;

            if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter))
            {
                return(false);
            }
            if (setterCall.Arguments.Count != getterCall.Arguments.Count + 1)
            {
                return(false);
            }
            // Ensure that same arguments are passed to getterCall and setterCall:
            for (int j = 0; j < getterCall.Arguments.Count; j++)
            {
                if (setterCall.Arguments[j].MatchStLoc(out var v) && v.IsSingleDefinition && v.LoadCount == 1)
                {
                    if (getterCall.Arguments[j].MatchLdLoc(v))
                    {
                        // OK, setter call argument is saved in temporary that is re-used for getter call
                        if (finalizeMatch == null)
                        {
                            finalizeMatch = AdjustArguments;
                        }
                        continue;
                    }
                }
                if (!SemanticHelper.IsPure(getterCall.Arguments[j].Flags))
                {
                    return(false);
                }
                if (!getterCall.Arguments[j].Match(setterCall.Arguments[j]).Success)
                {
                    return(false);
                }
            }
            return(true);

            void AdjustArguments(ILTransformContext context)
            {
                Debug.Assert(setterCall.Arguments.Count == getterCall.Arguments.Count + 1);
                for (int j = 0; j < getterCall.Arguments.Count; j++)
                {
                    if (setterCall.Arguments[j].MatchStLoc(out var v, out var value))
                    {
                        Debug.Assert(v.IsSingleDefinition && v.LoadCount == 1);
                        Debug.Assert(getterCall.Arguments[j].MatchLdLoc(v));
                        getterCall.Arguments[j] = value;
                    }
                }
            }
        }
        /// <summary>
        /// Matches Roslyn C# switch on nullable.
        /// </summary>
        bool MatchRoslynSwitchOnNullable(InstructionCollection <ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
        {
            newSwitch = null;
            // match first block:
            // if (logic.not(call get_HasValue(target))) br nullCaseBlock
            // br switchBlock
            if (!instructions[i].MatchIfInstruction(out var condition, out var trueInst))
            {
                return(false);
            }
            if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
            {
                return(false);
            }
            if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction target) || !SemanticHelper.IsPure(target.Flags))
            {
                return(false);
            }
            // match second block: switchBlock
            // note: I have seen cases where switchVar is inlined into the switch.
            // stloc switchVar(call GetValueOrDefault(ldloca tmp))
            // switch (ldloc switchVar) {
            //  case [0..1): br caseBlock1
            // ... more cases ...
            //  case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
            // }
            if (switchBlock.IncomingEdgeCount != 1)
            {
                return(false);
            }
            SwitchInstruction switchInst;

            switch (switchBlock.Instructions.Count)
            {
            case 2:
            {
                // this is the normal case described by the pattern above
                if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault))
                {
                    return(false);
                }
                if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1)
                {
                    return(false);
                }
                if (!(NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, out ILInstruction target2) && target2.Match(target).Success))
                {
                    return(false);
                }
                if (!(switchBlock.Instructions[1] is SwitchInstruction si))
                {
                    return(false);
                }
                switchInst = si;
                break;
            }

            case 1:
            {
                // this is the special case where `call GetValueOrDefault(ldloca tmp)` is inlined into the switch.
                if (!(switchBlock.Instructions[0] is SwitchInstruction si))
                {
                    return(false);
                }
                if (!(NullableLiftingTransform.MatchGetValueOrDefault(si.Value, out ILInstruction target2) && target2.Match(target).Success))
                {
                    return(false);
                }
                switchInst = si;
                break;
            }

            default:
            {
                return(false);
            }
            }
            ILInstruction switchValue;

            if (target.MatchLdLoca(out var v))
            {
                switchValue = new LdLoc(v).WithILRange(target);
            }
            else
            {
                switchValue = new LdObj(target, ((CallInstruction)getHasValue).Method.DeclaringType);
            }
            newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue);
            return(true);
        }
Beispiel #24
0
        public void Run(ILFunction function, ILTransformContext context)
        {
            ResetHasInitialValueFlag(function, context);
            // Remove dead stores to variables that are never read from.
            // If the stored value has some side-effect, the value is unwrapped.
            // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler.
            // In yield return + async, the C# compiler tends to store null/default(T) to variables
            // when the variable goes out of scope.
            if (function.IsAsync || function.IsIterator || context.Settings.RemoveDeadStores)
            {
                var variableQueue = new Queue <ILVariable>(function.Variables);
                while (variableQueue.Count > 0)
                {
                    var v = variableQueue.Dequeue();
                    if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot)
                    {
                        continue;
                    }
                    // Skip variables that are captured in a mcs yield state-machine
                    // loads of these will only be visible after DelegateConstruction step.
                    if (function.StateMachineCompiledWithMono && v.StateMachineField != null)
                    {
                        continue;
                    }
                    if (v.LoadCount != 0 || v.AddressCount != 0)
                    {
                        continue;
                    }
                    foreach (var stloc in v.StoreInstructions.OfType <StLoc>().ToArray())
                    {
                        if (stloc.Parent is Block block)
                        {
                            if (SemanticHelper.IsPure(stloc.Value.Flags))
                            {
                                block.Instructions.Remove(stloc);
                            }
                            else
                            {
                                stloc.ReplaceWith(stloc.Value);
                            }
                            if (stloc.Value is LdLoc ldloc)
                            {
                                variableQueue.Enqueue(ldloc.Variable);
                            }
                        }
                    }
                }
            }

            // Try to infer IType of stack slots that are of StackType.Ref:
            foreach (var v in function.Variables)
            {
                if (v.Kind == VariableKind.StackSlot && v.StackType == StackType.Ref && v.AddressCount == 0)
                {
                    IType newType = null;
                    // Multiple store are possible in case of (c ? ref a : ref b) += 1, for example.
                    foreach (var stloc in v.StoreInstructions.OfType <StLoc>())
                    {
                        var inferredType = stloc.Value.InferType(context.TypeSystem);
                        // cancel, if types of values do not match exactly
                        if (newType != null && !newType.Equals(inferredType))
                        {
                            newType = SpecialType.UnknownType;
                            break;
                        }
                        newType = inferredType;
                    }
                    // Only overwrite existing type, if a "better" type was found.
                    if (newType != null && newType != SpecialType.UnknownType)
                    {
                        v.Type = newType;
                    }
                }
            }
        }
Beispiel #25
0
        /// <summary>
        /// op is either add or remove/subtract:
        /// if (dynamic.isevent (target)) {
        ///     dynamic.invokemember.invokespecial.discard op_Name(target, value)
        /// } else {
        ///     dynamic.compound.op (dynamic.getmember Name(target), value)
        /// }
        /// =>
        /// dynamic.compound.op (dynamic.getmember Name(target), value)
        /// </summary>
        bool TransformDynamicAddAssignOrRemoveAssign(IfInstruction inst)
        {
            if (!inst.MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst))
            {
                return(false);
            }
            if (!(condition is DynamicIsEventInstruction isEvent))
            {
                return(false);
            }
            trueInst  = Block.Unwrap(trueInst);
            falseInst = Block.Unwrap(falseInst);
            if (!(falseInst is DynamicCompoundAssign dynamicCompoundAssign))
            {
                return(false);
            }
            if (!(dynamicCompoundAssign.Target is DynamicGetMemberInstruction getMember))
            {
                return(false);
            }
            if (!SemanticHelper.IsPure(isEvent.Argument.Flags))
            {
                return(false);
            }
            if (!isEvent.Argument.Match(getMember.Target).Success)
            {
                return(false);
            }
            if (!(trueInst is DynamicInvokeMemberInstruction invokeMember))
            {
                return(false);
            }
            if (!(invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.InvokeSpecialName) && invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.ResultDiscarded)))
            {
                return(false);
            }
            switch (dynamicCompoundAssign.Operation)
            {
            case ExpressionType.AddAssign:
                if (invokeMember.Name != "add_" + getMember.Name)
                {
                    return(false);
                }
                break;

            case ExpressionType.SubtractAssign:
                if (invokeMember.Name != "remove_" + getMember.Name)
                {
                    return(false);
                }
                break;

            default:
                return(false);
            }
            if (!dynamicCompoundAssign.Value.Match(invokeMember.Arguments[1]).Success)
            {
                return(false);
            }
            if (!invokeMember.Arguments[0].Match(getMember.Target).Success)
            {
                return(false);
            }
            context.Step("+= / -= dynamic.isevent pattern -> dynamic.compound.op", inst);
            inst.ReplaceWith(dynamicCompoundAssign);
            return(true);
        }
Beispiel #26
0
 /// <summary>
 /// Recursive function that lifts the specified instruction.
 /// The input instruction is expected to a subexpression of the trueInst
 /// (so that all nullableVars are guaranteed non-null within this expression).
 ///
 /// Creates a new lifted instruction without modifying the input instruction.
 /// On success, returns (new lifted instruction, bitset).
 /// If lifting fails, returns (null, null).
 ///
 /// The returned bitset specifies which nullableVars were considered "relevant" for this instruction.
 /// bitSet[i] == true means nullableVars[i] was relevant.
 ///
 /// The new lifted instruction will have equivalent semantics to the input instruction
 /// if all relevant variables are non-null [except that the result will be wrapped in a Nullable{T} struct].
 /// If any relevant variable is null, the new instruction is guaranteed to evaluate to <c>null</c>
 /// without having any other effect.
 /// </summary>
 (ILInstruction, BitSet) DoLift(ILInstruction inst)
 {
     if (MatchGetValueOrDefault(inst, out ILVariable inputVar))
     {
         // n.GetValueOrDefault() lifted => n.
         BitSet foundIndices = new BitSet(nullableVars.Count);
         for (int i = 0; i < nullableVars.Count; i++)
         {
             if (nullableVars[i] == inputVar)
             {
                 foundIndices[i] = true;
             }
         }
         if (foundIndices.Any())
         {
             return(new LdLoc(inputVar)
             {
                 ILRange = inst.ILRange
             }, foundIndices);
         }
         else
         {
             return(null, null);
         }
     }
     else if (inst is Conv conv)
     {
         var(arg, bits) = DoLift(conv.Argument);
         if (arg != null)
         {
             if (conv.HasFlag(InstructionFlags.MayThrow) && !bits.All(0, nullableVars.Count))
             {
                 // Cannot execute potentially-throwing instruction unless all
                 // the nullableVars are arguments to the instruction
                 // (thus causing it not to throw when any of them is null).
                 return(null, null);
             }
             var newInst = new Conv(arg, conv.InputType, conv.InputSign, conv.TargetType, conv.CheckForOverflow, isLifted: true)
             {
                 ILRange = conv.ILRange
             };
             return(newInst, bits);
         }
     }
     else if (inst is BinaryNumericInstruction binary)
     {
         var(left, leftBits)   = DoLift(binary.Left);
         var(right, rightBits) = DoLift(binary.Right);
         if (left != null && right == null && SemanticHelper.IsPure(binary.Right.Flags))
         {
             // Embed non-nullable pure expression in lifted expression.
             right = binary.Right.Clone();
         }
         if (left == null && right != null && SemanticHelper.IsPure(binary.Left.Flags))
         {
             // Embed non-nullable pure expression in lifted expression.
             left = binary.Left.Clone();
         }
         if (left != null && right != null)
         {
             var bits = leftBits ?? rightBits;
             if (rightBits != null)
             {
                 bits.UnionWith(rightBits);
             }
             if (binary.HasFlag(InstructionFlags.MayThrow) && !bits.All(0, nullableVars.Count))
             {
                 // Cannot execute potentially-throwing instruction unless all
                 // the nullableVars are arguments to the instruction
                 // (thus causing it not to throw when any of them is null).
                 return(null, null);
             }
             var newInst = new BinaryNumericInstruction(
                 binary.Operator, left, right,
                 binary.LeftInputType, binary.RightInputType,
                 binary.CheckForOverflow, binary.Sign,
                 isLifted: true
                 )
             {
                 ILRange = binary.ILRange
             };
             return(newInst, bits);
         }
     }
     return(null, null);
 }
Beispiel #27
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);
            }
        }