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); } }
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); }
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; }
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); }