Example #1
0
        private void EmitAssignmentPostfix(BoundAssignmentOperator assignment, LocalDefinition temp, UseKind useKind)
        {
            if (temp != null)
            {
                _builder.EmitLocalLoad(temp);
                FreeTemp(temp);
            }

            if (useKind == UseKind.UsedAsValue && assignment.RefKind != RefKind.None)
            {
                EmitLoadIndirect(assignment.Type, assignment.Syntax);
            }
        }
Example #2
0
        private void EmitAssignmentExpression(BoundAssignmentOperator assignmentOperator, UseKind useKind)
        {
            if (TryEmitAssignmentInPlace(assignmentOperator, useKind != UseKind.Unused))
            {
                Debug.Assert(assignmentOperator.RefKind == RefKind.None);
                return;
            }

            // Assignment expression codegen has the following parts:
            //
            // * PreRHS: We need to emit instructions before the load of the right hand side if:
            //   - If the left hand side is a ref local or ref formal parameter and the right hand 
            //     side is a value then we must put the ref on the stack early so that we can store 
            //     indirectly into it.
            //   - If the left hand side is an array slot then we must evaluate the array and indices
            //     before we evaluate the right hand side. We ensure that the array and indices are 
            //     on the stack when the store is executed.
            //   - Similarly, if the left hand side is a non-static field then its receiver must be
            //     evaluated before the right hand side.
            //
            // * RHS: There are three possible ways to do an assignment with respect to "refness", 
            //   and all are found in the lowering of:
            //
            //   N().s += 10;
            //
            //   That expression is realized as 
            //
            //   ref int addr = ref N().s;   // Assign a ref on the right hand side to the left hand side.
            //   int sum = addr + 10;        // No refs at all; assign directly to sum.
            //   addr = sum;                 // Assigns indirectly through the address.
            //
            //   - If we are in the first case then assignmentOperator.RefKind is Ref and the left hand side is a 
            //     ref local temporary. We simply assign the ref on the RHS to the storage on the LHS with no indirection.
            //
            //   - If we are in the second case then nothing is ref; we have a value on one side an a local on the other.
            //     Again, there is no indirection.
            // 
            //   - If we are in the third case then we have a ref on the left and a value on the right. We must compute the
            //     value of the right hand side and then store it into the left hand side.
            //
            // * Duplication: The result of an assignment operation is the value that was assigned. It is possible that 
            //   later codegen is expecting this value to be on the stack when we're done here. This is controlled by
            //   the "used" formal parameter. There are two possible cases:
            //   - If the preamble put stuff on the stack for the usage of the store, then we must not put an extra copy
            //     of the right hand side value on the stack; that will be between the value and the stuff needed to 
            //     do the storage. In that case we put the right hand side value in a temporary and restore it later.
            //   - Otherwise we can just do a dup instruction; there's nothing before the dup on the stack that we'll need.
            // 
            // * Storage: Either direct or indirect, depending. See the RHS section above for details.
            // 
            // * Post-storage: If we stashed away the duplicated value in the temporary, we need to restore it back to the stack.

            bool lhsUsesStack = EmitAssignmentPreamble(assignmentOperator);
            EmitAssignmentValue(assignmentOperator);
            LocalDefinition temp = EmitAssignmentDuplication(assignmentOperator, useKind, lhsUsesStack);
            EmitStore(assignmentOperator);
            EmitAssignmentPostfix(assignmentOperator, temp, useKind);
        }
Example #3
0
        private LocalDefinition EmitAssignmentDuplication(BoundAssignmentOperator assignmentOperator, UseKind useKind, bool lhsUsesStack)
        {
            LocalDefinition temp = null;
            if (useKind != UseKind.Unused)
            {
                _builder.EmitOpCode(ILOpCode.Dup);

                if (lhsUsesStack)
                {
                    // Today we sometimes have a case where we assign a ref directly to a temporary of ref type:
                    //
                    // ref int addr = ref N().y;  <-- copies the address by value; no indirection
                    // int sum = addr + 10;
                    // addr = sum;
                    //
                    // In "Redhawk" we can write this sort of code directly as well. However, we should
                    // never have a case where the value of the assignment is "used", either in our own
                    // lowering passes or in Redhawk. We never have something like:
                    //
                    // ref int t1 = (ref int t2 = ref M().s); 
                    //
                    // or the even more odd:
                    //
                    // int t1 = (ref int t2 = ref M().s);
                    //
                    // Therefore we don't have to worry about what if the temporary value we are stashing
                    // away is of ref type.
                    //
                    // If we ever do implement this sort of feature then we will need to figure out which
                    // of the situations above we are in, and ensure that the correct kind of temporary
                    // is created here. And also that either its value or its indirected value is read out
                    // after the store, in EmitAssignmentPostfix, below.

                    Debug.Assert(assignmentOperator.RefKind == RefKind.None);

                    temp = AllocateTemp(assignmentOperator.Left.Type, assignmentOperator.Left.Syntax);
                    _builder.EmitLocalStore(temp);
                }
            }
            return temp;
        }
Example #4
0
        private void EmitCallExpression(BoundCall call, UseKind useKind)
        {
            var method = call.Method;
            var receiver = call.ReceiverOpt;
            LocalDefinition tempOpt = null;

            // Calls to the default struct constructor are emitted as initobj, rather than call.
            // NOTE: constructor invocations are represented as BoundObjectCreationExpressions,
            // rather than BoundCalls.  This is why we can be confident that if we see a call to a
            // constructor, it has this very specific form.
            if (method.IsDefaultValueTypeConstructor())
            {
                Debug.Assert(method.IsImplicitlyDeclared);
                Debug.Assert(method.ContainingType == receiver.Type);
                Debug.Assert(receiver.Kind == BoundKind.ThisReference);

                tempOpt = EmitReceiverRef(receiver);
                _builder.EmitOpCode(ILOpCode.Initobj);    //  initobj  <MyStruct>
                EmitSymbolToken(method.ContainingType, call.Syntax);
                FreeOptTemp(tempOpt);

                return;
            }

            var arguments = call.Arguments;

            CallKind callKind;

            if (method.IsStatic)
            {
                callKind = CallKind.Call;
            }
            else
            {
                var receiverType = receiver.Type;

                if (receiverType.IsVerifierReference())
                {
                    tempOpt = EmitReceiverRef(receiver, isAccessConstrained: false);

                    // In some cases CanUseCallOnRefTypeReceiver returns true which means that 
                    // null check is unnecessary and we can use "call"
                    if (receiver.SuppressVirtualCalls ||
                        (!method.IsMetadataVirtual() && CanUseCallOnRefTypeReceiver(receiver)))
                    {
                        callKind = CallKind.Call;
                    }
                    else
                    {
                        callKind = CallKind.CallVirt;
                    }
                }
                else if (receiverType.IsVerifierValue())
                {
                    NamedTypeSymbol methodContainingType = method.ContainingType;
                    if (methodContainingType.IsVerifierValue() && MayUseCallForStructMethod(method))
                    {
                        // NOTE: this should be either a method which overrides some abstract method or 
                        //       does not override anything (with few exceptions, see MayUseCallForStructMethod); 
                        //       otherwise we should not use direct 'call' and must use constrained call;

                        // calling a method defined in a value type
                        Debug.Assert(receiverType == methodContainingType);
                        tempOpt = EmitReceiverRef(receiver);
                        callKind = CallKind.Call;
                    }
                    else
                    {
                        if (method.IsMetadataVirtual())
                        {
                            // When calling a method that is virtual in metadata on a struct receiver, 
                            // we use a constrained virtual call. If possible, it will skip boxing.
                            tempOpt = EmitReceiverRef(receiver, isAccessConstrained: true);
                            callKind = CallKind.ConstrainedCallVirt;
                        }
                        else
                        {
                            // calling a method defined in a base class.
                            EmitExpression(receiver, used: true);
                            EmitBox(receiverType, receiver.Syntax);
                            callKind = CallKind.Call;
                        }
                    }
                }
                else
                {
                    // receiver is generic and method must come from the base or an interface or a generic constraint
                    // if the receiver is actually a value type it would need to be boxed.
                    // let .constrained sort this out. 
                    callKind = receiverType.IsReferenceType && !IsRef(receiver) ?
                                CallKind.CallVirt :
                                CallKind.ConstrainedCallVirt;

                    tempOpt = EmitReceiverRef(receiver, isAccessConstrained: callKind == CallKind.ConstrainedCallVirt);
                }
            }

            // When emitting a callvirt to a virtual method we always emit the method info of the
            // method that first declared the virtual method, not the method info of an
            // overriding method. It would be a subtle breaking change to change that rule;
            // see bug 6156 for details.

            MethodSymbol actualMethodTargetedByTheCall = method;
            if (method.IsOverride && callKind != CallKind.Call)
            {
                actualMethodTargetedByTheCall = method.GetConstructedLeastOverriddenMethod(_method.ContainingType);
            }

            if (callKind == CallKind.ConstrainedCallVirt && actualMethodTargetedByTheCall.ContainingType.IsValueType)
            {
                // special case for overridden methods like ToString(...) called on
                // value types: if the original method used in emit cannot use callvirt in this
                // case, change it to Call.
                callKind = CallKind.Call;
            }

            // Devirtualizing of calls to effectively sealed methods.
            if (callKind == CallKind.CallVirt)
            {
                // NOTE: we check that we call method in same module just to be sure
                // that it cannot be recompiled as not final and make our call not verifiable. 
                // such change by adversarial user would arguably be a compat break, but better be safe...
                // In reality we would typically have one method calling another method in the same class (one GetEnumerator calling another).
                // Other scenarios are uncommon since base class cannot be sealed and 
                // referring to a derived type in a different module is not an easy thing to do.
                if (IsThisReceiver(receiver) && actualMethodTargetedByTheCall.ContainingType.IsSealed &&
                        (object)actualMethodTargetedByTheCall.ContainingModule == (object)_method.ContainingModule)
                {
                    // special case for target is in a sealed class and "this" receiver.
                    Debug.Assert(receiver.Type.IsVerifierReference());
                    callKind = CallKind.Call;
                }

                // NOTE: we do not check that we call method in same module.
                // Because of the "GetOriginalConstructedOverriddenMethod" above, the actual target
                // can only be final when it is "newslot virtual final".
                // In such case Dev11 emits "call" and we will just replicate the behavior. (see DevDiv: 546853 )
                else if (actualMethodTargetedByTheCall.IsMetadataFinal && CanUseCallOnRefTypeReceiver(receiver))
                {
                    // special case for calling 'final' virtual method on reference receiver
                    Debug.Assert(receiver.Type.IsVerifierReference());
                    callKind = CallKind.Call;
                }
            }

            EmitArguments(arguments, method.Parameters);
            int stackBehavior = GetCallStackBehavior(call);
            switch (callKind)
            {
                case CallKind.Call:
                    _builder.EmitOpCode(ILOpCode.Call, stackBehavior);
                    break;

                case CallKind.CallVirt:
                    _builder.EmitOpCode(ILOpCode.Callvirt, stackBehavior);
                    break;

                case CallKind.ConstrainedCallVirt:
                    _builder.EmitOpCode(ILOpCode.Constrained);
                    EmitSymbolToken(receiver.Type, receiver.Syntax);
                    _builder.EmitOpCode(ILOpCode.Callvirt, stackBehavior);
                    break;
            }

            EmitSymbolToken(actualMethodTargetedByTheCall, call.Syntax,
                            actualMethodTargetedByTheCall.IsVararg ? (BoundArgListOperator)call.Arguments[call.Arguments.Length - 1] : null);

            if (!method.ReturnsVoid)
            {
                EmitPopIfUnused(useKind != UseKind.Unused);
            }
            else if (_ilEmitStyle == ILEmitStyle.Debug)
            {
                // The only void methods with usable return values are constructors and we represent those
                // as BoundObjectCreationExpressions, not BoundCalls.
                Debug.Assert(useKind == UseKind.Unused, "Using the return value of a void method.");
                Debug.Assert(_method.GenerateDebugInfo, "Implied by this.emitSequencePoints");

                // DevDiv #15135.  When a method like System.Diagnostics.Debugger.Break() is called, the
                // debugger sees an event indicating that a user break (vs a breakpoint) has occurred.
                // When this happens, it uses ICorDebugILFrame.GetIP(out uint, out CorDebugMappingResult)
                // to determine the current instruction pointer.  This method returns the instruction
                // *after* the call.  The source location is then given by the last sequence point before
                // or on this instruction.  As a result, if the instruction after the call has its own
                // sequence point, then that sequence point will be used to determine the source location
                // and the debugging experience will be disrupted.  The easiest way to ensure that the next
                // instruction does not have a sequence point is to insert a nop.  Obviously, we only do this
                // if debugging is enabled and optimization is disabled.

                // From ILGENREC::genCall:
                //   We want to generate a NOP after CALL opcodes that end a statement so the debugger
                //   has better stepping behavior

                // CONSIDER: In the native compiler, there's an additional restriction on when this nop is
                // inserted.  It is quite complicated, but it basically seems to say that, if we thought
                // we could omit the temp-and-copy for a struct construction and it turned out that we
                // couldn't (perhaps because the assigned local was captured by a lambda), and if we're
                // not using the result of the constructor call (how can this even happen?), then we don't
                // want to insert the nop.  Since the consequence of not implementing this complicated logic
                // is an extra nop in debug code, this is likely not a priority.

                // CONSIDER: The native compiler also checks !(tree->flags & EXF_NODEBUGINFO).  We don't have
                // this mutable bit on our bound nodes, so we can't exactly match the behavior.  We might be
                // able to approximate the native behavior by inspecting call.WasCompilerGenerated, but it is
                // not in a reliable state after lowering.

                _builder.EmitOpCode(ILOpCode.Nop);
            }

            if (useKind == UseKind.UsedAsValue && method.RefKind != RefKind.None)
            {
                EmitLoadIndirect(method.ReturnType, call.Syntax);
            }
            else if (useKind == UseKind.UsedAsAddress)
            {
                Debug.Assert(method.RefKind != RefKind.None);
            }

            FreeOptTemp(tempOpt);
        }