private static void RewriteLocalDeclaration( CSharpCompilation compilation, EENamedTypeSymbol container, HashSet <LocalSymbol> declaredLocals, ArrayBuilder <BoundStatement> statements, BoundLocalDeclaration node) { Debug.Assert(node.ArgumentsOpt.IsDefault); var local = node.LocalSymbol; var syntax = node.Syntax; declaredLocals.Add(local); var voidType = compilation.GetSpecialType(SpecialType.System_Void); var objectType = compilation.GetSpecialType(SpecialType.System_Object); var typeType = compilation.GetWellKnownType(WellKnownType.System_Type); var stringType = compilation.GetSpecialType(SpecialType.System_String); // <>CreateVariable(Type type, string name) var method = container.GetOrAddSynthesizedMethod( ExpressionCompilerConstants.CreateVariableMethodName, (c, n, s) => new PlaceholderMethodSymbol( c, s, n, voidType, m => ImmutableArray.Create <ParameterSymbol>( new SynthesizedParameterSymbol(m, typeType, ordinal: 0, refKind: RefKind.None), new SynthesizedParameterSymbol(m, stringType, ordinal: 1, refKind: RefKind.None)))); var type = new BoundTypeOfOperator(syntax, new BoundTypeExpression(syntax, aliasOpt: null, type: local.Type), null, typeType); var name = new BoundLiteral(syntax, ConstantValue.Create(local.Name), stringType); var call = BoundCall.Synthesized( syntax, receiverOpt: null, method: method, arguments: ImmutableArray.Create <BoundExpression>(type, name)); statements.Add(new BoundExpressionStatement(syntax, call)); var initializer = node.InitializerOpt; if (initializer != null) { // Generate assignment to local. The assignment will // be rewritten in PlaceholderLocalRewriter. var assignment = new BoundAssignmentOperator( syntax, new BoundLocal(syntax, local, constantValueOpt: null, type: local.Type), initializer, RefKind.None, local.Type); statements.Add(new BoundExpressionStatement(syntax, assignment)); } }
private static void RewriteLocalDeclaration( CSharpCompilation compilation, EENamedTypeSymbol container, HashSet <LocalSymbol> declaredLocals, ArrayBuilder <BoundStatement> statements, BoundLocalDeclaration node) { Debug.Assert(node.ArgumentsOpt.IsDefault); var local = node.LocalSymbol; var syntax = node.Syntax; declaredLocals.Add(local); var typeType = compilation.GetWellKnownType(WellKnownType.System_Type); var stringType = compilation.GetSpecialType(SpecialType.System_String); var guidConstructor = (MethodSymbol)compilation.GetWellKnownTypeMember(WellKnownMember.System_Guid__ctor); // CreateVariable(Type type, string name) var method = PlaceholderLocalSymbol.GetIntrinsicMethod(compilation, ExpressionCompilerConstants.CreateVariableMethodName); var type = new BoundTypeOfOperator(syntax, new BoundTypeExpression(syntax, aliasOpt: null, type: local.Type), null, typeType); var name = new BoundLiteral(syntax, ConstantValue.Create(local.Name), stringType); bool hasCustomTypeInfoPayload; var customTypeInfoPayload = GetCustomTypeInfoPayload(local, syntax, compilation, out hasCustomTypeInfoPayload); var customTypeInfoPayloadId = GetCustomTypeInfoPayloadId(syntax, guidConstructor, hasCustomTypeInfoPayload); var call = BoundCall.Synthesized( syntax, receiverOpt: null, method: method, arguments: ImmutableArray.Create(type, name, customTypeInfoPayloadId, customTypeInfoPayload)); statements.Add(new BoundExpressionStatement(syntax, call)); var initializer = node.InitializerOpt; if (initializer != null) { // Generate assignment to local. The assignment will // be rewritten in PlaceholderLocalRewriter. var assignment = new BoundAssignmentOperator( syntax, new BoundLocal(syntax, local, constantValueOpt: null, type: local.Type), initializer, RefKind.None, local.Type); statements.Add(new BoundExpressionStatement(syntax, assignment)); } }
internal void Parse(BoundAssignmentOperator boundAssignmentOperator) { base.Parse(boundAssignmentOperator); var boundLocal = boundAssignmentOperator.Left as BoundLocal; if (boundLocal != null && boundLocal.Syntax.Parent != null) { var variableDeclarationSyntax = boundLocal.Syntax.Parent.Green as VariableDeclarationSyntax; if (variableDeclarationSyntax != null) { this.TypeDeclaration = true; this.ApplyAutoType = variableDeclarationSyntax.Type.ToString() == "var"; } } var forEachStatementSyntax = boundAssignmentOperator.Left.Syntax.Green as ForEachStatementSyntax; if (forEachStatementSyntax != null) { if (boundLocal != null && boundLocal.LocalSymbol.SynthesizedKind == default(SynthesizedLocalKind)) { this.TypeDeclaration = true; this.ApplyAutoType = true; } } this.Left = Deserialize(boundAssignmentOperator.Left) as Expression; this.Right = Deserialize(boundAssignmentOperator.Right) as Expression; if (boundLocal == null || boundLocal.LocalSymbol.IsFixed || boundLocal.LocalSymbol.IsUsing || boundLocal.LocalSymbol.SynthesizedKind == SynthesizedLocalKind.LoweringTemp) { this.TypeDeclaration = false; this.ApplyAutoType = false; } this.IsRef = boundAssignmentOperator.RefKind.HasFlag(RefKind.Ref); this.IsOut = boundAssignmentOperator.RefKind.HasFlag(RefKind.Out); if (this.IsRef || this.IsOut) { this.TypeDeclaration = true; } }
private static void RewriteLocalDeclaration( CSharpCompilation compilation, EENamedTypeSymbol container, HashSet<LocalSymbol> declaredLocals, ArrayBuilder<BoundStatement> statements, BoundLocalDeclaration node) { Debug.Assert(node.ArgumentsOpt.IsDefault); var local = node.LocalSymbol; var syntax = node.Syntax; declaredLocals.Add(local); var typeType = compilation.GetWellKnownType(WellKnownType.System_Type); var stringType = compilation.GetSpecialType(SpecialType.System_String); // CreateVariable(Type type, string name) var method = PlaceholderLocalSymbol.GetIntrinsicMethod(compilation, ExpressionCompilerConstants.CreateVariableMethodName); var type = new BoundTypeOfOperator(syntax, new BoundTypeExpression(syntax, aliasOpt: null, type: local.Type), null, typeType); var name = new BoundLiteral(syntax, ConstantValue.Create(local.Name), stringType); var call = BoundCall.Synthesized( syntax, receiverOpt: null, method: method, arguments: ImmutableArray.Create<BoundExpression>(type, name)); statements.Add(new BoundExpressionStatement(syntax, call)); var initializer = node.InitializerOpt; if (initializer != null) { // Generate assignment to local. The assignment will // be rewritten in PlaceholderLocalRewriter. var assignment = new BoundAssignmentOperator( syntax, new BoundLocal(syntax, local, constantValueOpt: null, type: local.Type), initializer, RefKind.None, local.Type); statements.Add(new BoundExpressionStatement(syntax, assignment)); } }
private static void RewriteLocalDeclaration( ArrayBuilder<BoundStatement> statements, BoundLocalDeclaration node) { Debug.Assert(node.ArgumentsOpt.IsDefault); var initializer = node.InitializerOpt; if (initializer != null) { var local = node.LocalSymbol; var syntax = node.Syntax; // Generate assignment to local. The assignment will // be rewritten in PlaceholderLocalRewriter. var assignment = new BoundAssignmentOperator( syntax, new BoundLocal(syntax, local, constantValueOpt: null, type: local.Type), initializer, RefKind.None, local.Type); statements.Add(new BoundExpressionStatement(syntax, assignment)); } }
private static void RewriteLocalDeclaration( ArrayBuilder <BoundStatement> statements, BoundLocalDeclaration node) { Debug.Assert(node.ArgumentsOpt.IsDefault); var initializer = node.InitializerOpt; if (initializer != null) { var local = node.LocalSymbol; var syntax = node.Syntax; // Generate assignment to local. The assignment will // be rewritten in PlaceholderLocalRewriter. var assignment = new BoundAssignmentOperator( syntax, new BoundLocal(syntax, local, constantValueOpt: null, type: local.Type), initializer, RefKind.None, local.Type); statements.Add(new BoundExpressionStatement(syntax, assignment)); } }
// indirect assignment is assignment to a value referenced indirectly // it may only happen if // 1) lhs is a reference (must be a parameter or a local) // 2) it is not a ref/out assignment where the reference itself would be assigned private static bool IsIndirectAssignment(BoundAssignmentOperator node) { var lhs = node.Left; switch (lhs.Kind) { case BoundKind.ThisReference: Debug.Assert(lhs.Type.IsValueType && node.RefKind == RefKind.None); return true; case BoundKind.Parameter: if (((BoundParameter)lhs).ParameterSymbol.RefKind != RefKind.None) { bool isIndirect = node.RefKind == RefKind.None; Debug.Assert(isIndirect, "direct assignment to a ref/out parameter is highly suspicious"); return isIndirect; } break; case BoundKind.Local: if (((BoundLocal)lhs).LocalSymbol.RefKind != RefKind.None) { bool isIndirect = node.RefKind == RefKind.None; return isIndirect; } break; } Debug.Assert(node.RefKind == RefKind.None, "this is not something that can be assigned indirectly"); return false; }
private static bool IsIndirectOrInstanceFieldAssignment(BoundAssignmentOperator node) { var lhs = node.Left; if (lhs.Kind == BoundKind.FieldAccess) { return !((BoundFieldAccess)lhs).FieldSymbol.IsStatic; } return IsIndirectAssignment(node); }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { LocalDefUseInfo locInfo; var left = node.Left as BoundLocal; // store to something that is not special. (operands still could be rewritten) if (left == null || !_info.TryGetValue(left.LocalSymbol, out locInfo)) { return base.VisitAssignmentOperator(node); } // indirect local store is not special. (operands still could be rewritten) // NOTE: if Lhs is a stack local, it will be handled as a read and possibly duped. var indirectStore = left.LocalSymbol.RefKind != RefKind.None && node.RefKind == RefKind.None; if (indirectStore) { return base.VisitAssignmentOperator(node); } // == here we have a regular write to a stack local // // we do not need to visit lhs, because we do not read the local, // just update the counter to be in sync. // // if this is the last store, we just push the rhs // otherwise record a store. // fake visiting of left _nodeCounter += 1; // visit right var right = (BoundExpression)Visit(node.Right); // do actual assignment Debug.Assert(locInfo.LocalDefs.Any((d) => _nodeCounter == d.start && _nodeCounter <= d.end)); var isLast = IsLastAccess(locInfo, _nodeCounter); if (isLast) { // assigned local is not used later => just emit the Right return right; } else { // assigned local used later - keep assignment. // codegen will keep value on stack when sees assignment "stackLocal = expr" return node.Update(left, right, node.RefKind, node.Type); } }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { var sequence = node.Left as BoundSequence; if (sequence != null) { // assigning to a sequence is uncommon, but could happen in a // case if LHS was a declaration expression. // // Just rewrite {se1, se2, se3, val} = something // into ==> {se1, se2, se3, val = something} BoundExpression rewritten = sequence.Update(sequence.Locals, sequence.SideEffects, node.Update(sequence.Value, node.Right, node.RefKind, node.Type), sequence.Type); rewritten = (BoundExpression)Visit(rewritten); // do not count the assignment twice _counter--; return rewritten; } var isIndirectAssignment = IsIndirectAssignment(node); var left = VisitExpression(node.Left, isIndirectAssignment ? ExprContext.Address : ExprContext.AssignmentTarget); // must delay recording a write until after RHS is evaluated var assignmentLocal = _assignmentLocal; _assignmentLocal = null; Debug.Assert(_context != ExprContext.AssignmentTarget, "assignment expression cannot be a target of another assignment"); ExprContext rhsContext; if (node.RefKind != RefKind.None || _context == ExprContext.Address) { // we need the address of rhs one way or another so we cannot have it on the stack. rhsContext = ExprContext.Address; } else { Debug.Assert(_context == ExprContext.Value || _context == ExprContext.Box || _context == ExprContext.Sideeffects, "assignment expression cannot be a target of another assignment"); // we only need a value of rhs, so if otherwise possible it can be a stack value. rhsContext = ExprContext.Value; } // if right is a struct ctor, it may be optimized into in-place call // Such call will push the receiver ref before the arguments // so we need to ensure that arguments cannot use stack temps BoundExpression right = node.Right; object rhsCookie = null; if (right.Kind == BoundKind.ObjectCreationExpression && right.Type.IsVerifierValue() && ((BoundObjectCreationExpression)right).Constructor.ParameterCount != 0) { rhsCookie = this.GetStackStateCookie(); } right = VisitExpression(node.Right, rhsContext); // if assigning to a local, now it is the time to record the Write if (assignmentLocal != null) { // This assert will fire if code relies on implicit CLR coercions // - i.e assigns int value to a short local. // in that case we should force lhs to be a real local. Debug.Assert( node.Left.Type.Equals(node.Right.Type, ignoreCustomModifiersAndArraySizesAndLowerBounds: true, ignoreDynamic: true), @"type of the assignment value is not the same as the type of assignment target. This is not expected by the optimizer and is typically a result of a bug somewhere else."); Debug.Assert(!isIndirectAssignment, "indirect assignment is a read, not a write"); LocalSymbol localSymbol = assignmentLocal.LocalSymbol; // Special Case: If the RHS is a pointer conversion, then the assignment functions as // a conversion (because the RHS will actually be typed as a native u/int in IL), so // we should not optimize away the local (i.e. schedule it on the stack). if (CanScheduleToStack(localSymbol) && assignmentLocal.Type.IsPointerType() && right.Kind == BoundKind.Conversion && ((BoundConversion)right).ConversionKind.IsPointerConversion()) { ShouldNotSchedule(localSymbol); } RecordVarWrite(localSymbol); assignmentLocal = null; } if (rhsCookie != null) { // we currently have the rhs on stack, adjust for that. PopEvalStack(); this.EnsureStackState(rhsCookie); } return node.Update(left, right, node.RefKind, node.Type); }
private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bool used) { // Avoid rewriting if node has errors since at least // one of the operands is invalid. if (node.HasErrors) { return(node); } var propertyAccessor = node.Left as BoundPropertyAccess; if (propertyAccessor == null) { return((BoundExpression)base.VisitAssignmentOperator(node)); } // Rewrite property assignment into call to setter. var property = propertyAccessor.PropertySymbol.GetBaseProperty(); var setMethod = property.SetMethod; Debug.Assert(setMethod != null); Debug.Assert(setMethod.Parameters.Count == 1); Debug.Assert(!setMethod.IsOverride); var rewrittenReceiver = (BoundExpression)Visit(propertyAccessor.ReceiverOpt); var rewrittenArgument = (BoundExpression)Visit(node.Right); if (used) { // Save expression value to a temporary before calling the // setter, and restore the temporary after the setter, so the // assignment can be used as an embedded expression. var exprType = rewrittenArgument.Type; var tempSymbol = new TempLocalSymbol(exprType, RefKind.None, containingSymbol); var tempLocal = new BoundLocal(null, null, tempSymbol, null, exprType); var saveTemp = new BoundAssignmentOperator( null, null, tempLocal, rewrittenArgument, exprType); var call = BoundCall.SynthesizedCall( rewrittenReceiver, setMethod, saveTemp); return(new BoundSequence( node.Syntax, node.SyntaxTree, ReadOnlyArray <LocalSymbol> .CreateFrom(tempSymbol), ReadOnlyArray <BoundExpression> .CreateFrom(call), tempLocal, exprType)); } else { return(BoundCall.SynthesizedCall( rewrittenReceiver, setMethod, rewrittenArgument)); } }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { BoundExpression originalLeft = node.Left; if (originalLeft.Kind != BoundKind.Local) { return(base.VisitAssignmentOperator(node)); } var leftLocal = (BoundLocal)originalLeft; BoundExpression originalRight = node.Right; if (leftLocal.LocalSymbol.RefKind != RefKind.None && node.RefKind != RefKind.None && NeedsProxy(leftLocal.LocalSymbol)) { Debug.Assert(!proxies.ContainsKey(leftLocal.LocalSymbol)); Debug.Assert(!IsStackAlloc(originalRight)); //spilling ref local variables throw ExceptionUtilities.Unreachable; } if (NeedsProxy(leftLocal.LocalSymbol) && !proxies.ContainsKey(leftLocal.LocalSymbol)) { Debug.Assert(leftLocal.LocalSymbol.DeclarationKind == LocalDeclarationKind.None); // spilling temp variables throw ExceptionUtilities.Unreachable; } BoundExpression rewrittenLeft = (BoundExpression)this.Visit(leftLocal); BoundExpression rewrittenRight = (BoundExpression)this.Visit(originalRight); TypeSymbol rewrittenType = VisitType(node.Type); // Check if we're assigning the result of stackalloc to a hoisted local. // If we are, we need to store the result in a temp local and then assign // the value of the local to the field corresponding to the hoisted local. // If the receiver of the field is on the stack when the stackalloc happens, // popping it will free the memory (?) or otherwise cause verification issues. // DevDiv Bugs 59454 if (rewrittenLeft.Kind != BoundKind.Local && IsStackAlloc(originalRight)) { // From ILGENREC::genAssign: // DevDiv Bugs 59454: Handle hoisted local initialized with a stackalloc // NOTE: Need to check for cast of stackalloc on RHS. // If LHS isLocal, then genAddr is a noop so regular case works fine. SyntheticBoundNodeFactory factory = new SyntheticBoundNodeFactory(this.CurrentMethod, rewrittenLeft.Syntax, this.CompilationState, this.Diagnostics); BoundAssignmentOperator tempAssignment; BoundLocal tempLocal = factory.StoreToTemp(rewrittenRight, out tempAssignment); Debug.Assert(node.RefKind == RefKind.None); BoundAssignmentOperator rewrittenAssignment = node.Update(rewrittenLeft, tempLocal, node.RefKind, rewrittenType); return(new BoundSequence( node.Syntax, ImmutableArray.Create <LocalSymbol>(tempLocal.LocalSymbol), ImmutableArray.Create <BoundExpression>(tempAssignment), rewrittenAssignment, rewrittenType)); } return(node.Update(rewrittenLeft, rewrittenRight, node.RefKind, rewrittenType)); }
private void EmitStore(BoundAssignmentOperator assignment) { BoundExpression expression = assignment.Left; switch (expression.Kind) { case BoundKind.FieldAccess: EmitFieldStore((BoundFieldAccess)expression); break; case BoundKind.Local: // If we are doing a 'normal' local assignment like 'int t = 10;', or // if we are initializing a temporary like 'ref int t = ref M().s;' then // we just emit a local store. If we are doing an assignment through // a ref local temporary then we assume that the instruction to load // the address is already on the stack, and we must indirect through it. // See the comments in EmitAssignmentExpression above for details. BoundLocal local = (BoundLocal)expression; if (local.LocalSymbol.RefKind != RefKind.None && assignment.RefKind == RefKind.None) { EmitIndirectStore(local.LocalSymbol.Type, local.Syntax); } else { if (IsStackLocal(local.LocalSymbol)) { // assign to stack var == leave original value on stack break; } else { _builder.EmitLocalStore(GetLocal(local)); } } break; case BoundKind.ArrayAccess: var array = ((BoundArrayAccess)expression).Expression; var arrayType = (ArrayTypeSymbol)array.Type; EmitArrayElementStore(arrayType, expression.Syntax); break; case BoundKind.ThisReference: EmitThisStore((BoundThisReference)expression); break; case BoundKind.Parameter: EmitParameterStore((BoundParameter)expression); break; case BoundKind.Dup: Debug.Assert(((BoundDup)expression).RefKind != RefKind.None); EmitIndirectStore(expression.Type, expression.Syntax); break; case BoundKind.RefValueOperator: case BoundKind.PointerIndirectionOperator: case BoundKind.PseudoVariable: EmitIndirectStore(expression.Type, expression.Syntax); break; case BoundKind.Sequence: { var sequence = (BoundSequence)expression; EmitStore(assignment.Update(sequence.Value, assignment.Right, assignment.RefKind, assignment.Type)); var notReleased = DigForValueLocal(sequence); if (notReleased != null) { FreeLocal(notReleased); } } break; case BoundKind.Call: Debug.Assert(((BoundCall)expression).Method.RefKind != RefKind.None); EmitIndirectStore(expression.Type, expression.Syntax); break; case BoundKind.ModuleVersionId: EmitModuleVersionIdStore((BoundModuleVersionId)expression); break; case BoundKind.InstrumentationPayloadRoot: EmitInstrumentationPayloadRootStore((BoundInstrumentationPayloadRoot)expression); break; case BoundKind.PreviousSubmissionReference: // Script references are lowered to a this reference and a field access. default: throw ExceptionUtilities.UnexpectedValue(expression.Kind); } }
// sometimes it is possible and advantageous to get an address of the lHS and // perform assignment as an in-place initialization via initobj or constructor invocation. // // 1) initobj // is used when assigning default value to T that is not a verifier reference. // // 2) in-place ctor call // is used when assigning a freshly created struct. "x = new S(arg)" can be // replaced by x.S(arg) as long as partial assignment cannot be observed - // i.e. target must not be on the heap and we should not be in a try block. private bool TryEmitAssignmentInPlace(BoundAssignmentOperator assignmentOperator, bool used) { var left = assignmentOperator.Left; // if result is used, and lives on heap, we must keep RHS value on the stack. // otherwise we can try conjuring up the RHS value directly where it belongs. if (used && !TargetIsNotOnHeap(left)) { return false; } if (!SafeToGetWriteableReference(left)) { // cannot take a ref return false; } var right = assignmentOperator.Right; var rightType = right.Type; // in-place is not advantageous for reference types or constants if (!rightType.IsTypeParameter()) { if (rightType.IsReferenceType || (right.ConstantValue != null && rightType.SpecialType != SpecialType.System_Decimal)) { return false; } } if (right.IsDefaultValue()) { InPlaceInit(left, used); return true; } if (right.Kind == BoundKind.ObjectCreationExpression) { // It is desirable to do in-place ctor call if possible. // we could do newobj/stloc, but in-place call // produces same or better code in current JITs if (PartialCtorResultCannotEscape(left)) { var objCreation = (BoundObjectCreationExpression)right; InPlaceCtorCall(left, objCreation, used); return true; } } return false; }
// indirect assignment is assignment to a value referenced indirectly // it may only happen if // 1) lhs is a reference (must be a parameter or a local) // 2) it is not a ref/out assignment where the reference itself would be assigned private static bool IsIndirectAssignment(BoundAssignmentOperator node) { var lhs = node.Left; Debug.Assert(node.RefKind == RefKind.None || (lhs as BoundLocal)?.LocalSymbol.RefKind == RefKind.Ref, "only ref locals can be a target of a ref assignment"); switch (lhs.Kind) { case BoundKind.ThisReference: Debug.Assert(lhs.Type.IsValueType, "'this' is assignable only in structs"); return true; case BoundKind.Parameter: if (((BoundParameter)lhs).ParameterSymbol.RefKind != RefKind.None) { bool isIndirect = node.RefKind == RefKind.None; return isIndirect; } return false; case BoundKind.Local: if (((BoundLocal)lhs).LocalSymbol.RefKind != RefKind.None) { bool isIndirect = node.RefKind == RefKind.None; return isIndirect; } return false; case BoundKind.Call: Debug.Assert(((BoundCall)lhs).Method.RefKind == RefKind.Ref, "only ref returning methods are assignable"); return true; case BoundKind.AssignmentOperator: Debug.Assert(((BoundAssignmentOperator)lhs).RefKind == RefKind.Ref, "only ref assignments are assignable"); return true; case BoundKind.Sequence: Debug.Assert(!IsIndirectAssignment(node.Update(((BoundSequence)node.Left).Value, node.Right, node.RefKind, node.Type)), "indirect assignment to a sequence is unexpected"); return false; case BoundKind.RefValueOperator: case BoundKind.PointerIndirectionOperator: case BoundKind.PseudoVariable: return true; case BoundKind.ModuleVersionId: case BoundKind.InstrumentationPayloadRoot: // these are just stores into special static fields goto case BoundKind.FieldAccess; case BoundKind.FieldAccess: case BoundKind.ArrayAccess: // always symbolic stores return false; default: throw ExceptionUtilities.UnexpectedValue(lhs.Kind); } }
private void EmitAssignmentValue(BoundAssignmentOperator assignmentOperator) { if (assignmentOperator.RefKind == RefKind.None) { EmitExpression(assignmentOperator.Right, used: true); } else { // LEAKING A TEMP IS OK HERE // generally taking a ref for the purpose of ref assignment should not be done on homeless values // however, there are very rare cases when we need to get a ref off a copy in synthetic code and we have to leak those. // fortunately these are very short-lived temps that should not cause value sharing. var temp = EmitAddress(assignmentOperator.Right, AddressKind.Writeable); #if DEBUG Debug.Assert(temp == null || ((SynthesizedLocal)assignmentOperator.Left.ExpressionSymbol).SynthesizedKind == SynthesizedLocalKind.LoweringTemp); #endif } }
public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) { if (node.HasErrors) { return(node); } // There are five possible cases. // // Case 1: receiver.Prop += value is transformed into // temp = receiver // temp.Prop = temp.Prop + value // and a later rewriting will turn that into calls to getters and setters. // // Case 2: collection[i1, i2, i3] += value is transformed into // tc = collection // t1 = i1 // t2 = i2 // t3 = i3 // tc[t1, t2, t3] = tc[t1, t2, t3] + value // and again, a later rewriting will turn that into getters and setters of the indexer. // // Case 3: local += value (and param += value) needs no temporaries; it simply // becomes local = local + value. // // Case 4: staticField += value needs no temporaries either. However, classInst.field += value becomes // temp = classInst // temp.field = temp.field + value // // Case 5: otherwise, it must be structVariable.field += value or array[index] += value. Either way // we have a variable on the left. Transform it into: // ref temp = ref variable // temp = temp + value var temps = ArrayBuilder <LocalSymbol> .GetInstance(); var stores = ArrayBuilder <BoundExpression> .GetInstance(); // This will be filled in with the LHS that uses temporaries to prevent // double-evaluation of side effects. BoundExpression transformedLHS = null; if (node.Left.Kind == BoundKind.PropertyAccess) { // We need to stash away the receiver so that it does not get evaluated twice. // If the receiver is classified as a value of reference type then we can simply say // // R temp = receiver // temp.prop = temp.prop + rhs // // But if the receiver is classified as a variable of struct type then we // cannot make a copy of the value; we need to make sure that we mutate // the original receiver, not the copy. We have to generate // // ref R temp = ref receiver // temp.prop = temp.prop + rhs // // The rules of C# (in section 7.17.1) require that if you have receiver.prop // as the target of an assignment such that receiver is a value type, it must // be classified as a variable. If we've gotten this far in the rewriting, // assume that was the case. var prop = (BoundPropertyAccess)node.Left; // If the property is static then we can just generate prop = prop + value if (prop.ReceiverOpt == null) { transformedLHS = prop; } else { // Can we ever avoid storing the receiver in a temp? If the receiver is a variable then it // might be modified by the computation of the getter, the value, or the operation. // The receiver cannot be a null constant or constant of value type. It could be a // constant of string type, but there are no mutable properties of a string. // Similarly, there are no mutable properties of a Type object, so the receiver // cannot be a typeof(T) expression. The only situation I can think of where we could // optimize away the temp is if the receiver is a readonly field of reference type, // we are not in a constructor, and the receiver of the *field*, if any, is also idempotent. // It doesn't seem worthwhile to pursue an optimization for this exceedingly rare case. var rewrittenReceiver = (BoundExpression)Visit(prop.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundPropertyAccess(prop.Syntax, prop.SyntaxTree, receiverTemp.Item2, prop.PropertySymbol, prop.Type); } } else if (node.Left.Kind == BoundKind.IndexerAccess) { var indexer = (BoundIndexerAccess)node.Left; BoundExpression transformedReceiver = null; if (indexer.ReceiverOpt != null) { var rewrittenReceiver = (BoundExpression)Visit(indexer.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); transformedReceiver = receiverTemp.Item2; stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); } // UNDONE: Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // UNDONE: we have to preserve both the source-code order of the side effects and the side effects // UNDONE: only being executed once. // UNDONE: // UNDONE: This is a subtly different problem than the problem faced by the conventional call // UNDONE: rewriter; with the conventional call rewriter we already know that the side effects // UNDONE: will only be executed once because the arguments are only being pushed on the stack once. // UNDONE: In a compound equality operator on an indexer the indices are placed on the stack twice. // UNDONE: That is to say, if you have: // UNDONE: // UNDONE: C().M(z : Z(), x : X(), y : Y()) // UNDONE: // UNDONE: then we can rewrite that into // UNDONE: // UNDONE: tempc = C() // UNDONE: tempz = Z() // UNDONE: tempc.M(X(), Y(), tempz) // UNDONE: // UNDONE: See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // UNDONE: temporaries in // UNDONE: // UNDONE: C().Collection[z : Z(), x : X(), y : Y()] += 1; // UNDONE: // UNDONE: because we have to ensure not just that Z() happens first, but in additioan that X() and Y() are only // UNDONE: called once. We have to generate this as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempz = Z() // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // UNDONE: // UNDONE: Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // UNDONE: However, we can still do the optimization where constants are not stored in // UNDONE: temporaries; if we have // UNDONE: // UNDONE: C().Collection[z : 123, y : Y(), x : X()] += 1; // UNDONE: // UNDONE: Then we can generate that as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; // UNDONE: // UNDONE: For now, we'll punt on both problems, as indexers are not implemented yet anyway. // UNDONE: We'll just generate one temporary for each argument. This will work, but in the // UNDONE: subsequent rewritings will generate more unnecessary temporaries. var transformedArguments = ArrayBuilder <BoundExpression> .GetInstance(); foreach (var argument in indexer.Arguments) { var rewrittenArgument = (BoundExpression)Visit(argument); var argumentTemp = TempHelpers.StoreToTemp(rewrittenArgument, RefKind.None, containingSymbol); transformedArguments.Add(argumentTemp.Item2); stores.Add(argumentTemp.Item1); temps.Add(argumentTemp.Item2.LocalSymbol); } transformedLHS = new BoundIndexerAccess(indexer.Syntax, indexer.SyntaxTree, transformedArguments.ToReadOnlyAndFree(), transformedReceiver, indexer.IndexerSymbol, indexer.Type); } else if (node.Left.Kind == BoundKind.Local || node.Left.Kind == BoundKind.Parameter) { // No temporaries are needed. Just generate local = local + value transformedLHS = node.Left; } else if (node.Left.Kind == BoundKind.FieldAccess) { // * If the field is static then no temporaries are needed. // * If the field is not static and the receiver is of reference type then generate t = r; t.f = t.f + value // * If the field is not static and the receiver is a variable of value type then we'll fall into the // general variable case below. var fieldAccess = (BoundFieldAccess)node.Left; if (fieldAccess.ReceiverOpt == null) { transformedLHS = fieldAccess; } else if (!fieldAccess.ReceiverOpt.Type.IsValueType) { var rewrittenReceiver = (BoundExpression)Visit(fieldAccess.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundFieldAccess(fieldAccess.Syntax, fieldAccess.SyntaxTree, receiverTemp.Item2, fieldAccess.FieldSymbol, null); } } if (transformedLHS == null) { // We made no transformation above. Either we have array[index] += value or // structVariable.field += value; either way we have a potentially complicated variable- // producing expression on the left. Generate // ref temp = ref variable; temp = temp + value var rewrittenVariable = (BoundExpression)Visit(node.Left); var variableTemp = TempHelpers.StoreToTemp(rewrittenVariable, RefKind.Ref, containingSymbol); stores.Add(variableTemp.Item1); temps.Add(variableTemp.Item2.LocalSymbol); transformedLHS = variableTemp.Item2; } // OK, we now have the temporary declarations, the temporary stores, and the transformed left hand side. // We need to generate // // xlhs = (FINAL)((LEFT)xlhs op rhs) // // And then wrap it up with the generated temporaries. // // (The right hand side has already been converted to the type expected by the operator.) BoundExpression opLHS = BoundConversion.SynthesizedConversion(transformedLHS, node.LeftConversion, node.Operator.LeftType); Debug.Assert(node.Right.Type == node.Operator.RightType); BoundExpression op = new BoundBinaryOperator(null, null, node.Operator.Kind, opLHS, node.Right, null, node.Operator.ReturnType); BoundExpression opFinal = BoundConversion.SynthesizedConversion(op, node.FinalConversion, node.Left.Type); BoundExpression assignment = new BoundAssignmentOperator(null, null, transformedLHS, opFinal, node.Left.Type); // OK, at this point we have: // // * temps evaluating and storing portions of the LHS that must be evaluated only once. // * the "transformed" left hand side, rebuilt to use temps where necessary // * the assignment "xlhs = (FINAL)((LEFT)xlhs op (RIGHT)rhs)" // // Notice that we have recursively rewritten the bound nodes that are things stored in // the temps, but we might have more rewriting to do on the assignment. There are three // conversions in there that might be lowered to method calls, an operator that might // be lowered to delegate combine, string concat, and so on, and don't forget, we // haven't lowered the right hand side at all! Let's rewrite all these things at once. BoundExpression rewrittenAssignment = (BoundExpression)Visit(assignment); BoundExpression result = (temps.Count == 0) ? rewrittenAssignment : new BoundSequence(null, null, temps.ToReadOnly(), stores.ToReadOnly(), rewrittenAssignment, rewrittenAssignment.Type); temps.Free(); stores.Free(); return(result); }
private void EmitAssignmentValue(BoundAssignmentOperator assignmentOperator) { if (assignmentOperator.RefKind == RefKind.None) { EmitExpression(assignmentOperator.Right, used: true); } else { // LEAKING A TEMP IS OK HERE // Once a reference is assigned to a byref local, there is no easy way to figure when // reference is no longer in use. // Therefore if we had to create a temp while producing a reference, we do not know when // the temp slot can be reused so we must leak it. var temp = EmitAddress(assignmentOperator.Right, AddressKind.Writeable); } }
public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) { if (node.HasErrors) { return node; } // There are five possible cases. // // Case 1: receiver.Prop += value is transformed into // temp = receiver // temp.Prop = temp.Prop + value // and a later rewriting will turn that into calls to getters and setters. // // Case 2: collection[i1, i2, i3] += value is transformed into // tc = collection // t1 = i1 // t2 = i2 // t3 = i3 // tc[t1, t2, t3] = tc[t1, t2, t3] + value // and again, a later rewriting will turn that into getters and setters of the indexer. // // Case 3: local += value (and param += value) needs no temporaries; it simply // becomes local = local + value. // // Case 4: staticField += value needs no temporaries either. However, classInst.field += value becomes // temp = classInst // temp.field = temp.field + value // // Case 5: otherwise, it must be structVariable.field += value or array[index] += value. Either way // we have a variable on the left. Transform it into: // ref temp = ref variable // temp = temp + value var temps = ArrayBuilder<LocalSymbol>.GetInstance(); var stores = ArrayBuilder<BoundExpression>.GetInstance(); // This will be filled in with the LHS that uses temporaries to prevent // double-evaluation of side effects. BoundExpression transformedLHS = null; if (node.Left.Kind == BoundKind.PropertyAccess) { // We need to stash away the receiver so that it does not get evaluated twice. // If the receiver is classified as a value of reference type then we can simply say // // R temp = receiver // temp.prop = temp.prop + rhs // // But if the receiver is classified as a variable of struct type then we // cannot make a copy of the value; we need to make sure that we mutate // the original receiver, not the copy. We have to generate // // ref R temp = ref receiver // temp.prop = temp.prop + rhs // // The rules of C# (in section 7.17.1) require that if you have receiver.prop // as the target of an assignment such that receiver is a value type, it must // be classified as a variable. If we've gotten this far in the rewriting, // assume that was the case. var prop = (BoundPropertyAccess)node.Left; // If the property is static then we can just generate prop = prop + value if (prop.ReceiverOpt == null) { transformedLHS = prop; } else { // Can we ever avoid storing the receiver in a temp? If the receiver is a variable then it // might be modified by the computation of the getter, the value, or the operation. // The receiver cannot be a null constant or constant of value type. It could be a // constant of string type, but there are no mutable properties of a string. // Similarly, there are no mutable properties of a Type object, so the receiver // cannot be a typeof(T) expression. The only situation I can think of where we could // optimize away the temp is if the receiver is a readonly field of reference type, // we are not in a constructor, and the receiver of the *field*, if any, is also idempotent. // It doesn't seem worthwhile to pursue an optimization for this exceedingly rare case. var rewrittenReceiver = (BoundExpression)Visit(prop.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundPropertyAccess(prop.Syntax, prop.SyntaxTree, receiverTemp.Item2, prop.PropertySymbol, prop.Type); } } else if (node.Left.Kind == BoundKind.IndexerAccess) { var indexer = (BoundIndexerAccess)node.Left; BoundExpression transformedReceiver = null; if (indexer.ReceiverOpt != null) { var rewrittenReceiver = (BoundExpression)Visit(indexer.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); transformedReceiver = receiverTemp.Item2; stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); } // UNDONE: Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // UNDONE: we have to preserve both the source-code order of the side effects and the side effects // UNDONE: only being executed once. // UNDONE: // UNDONE: This is a subtly different problem than the problem faced by the conventional call // UNDONE: rewriter; with the conventional call rewriter we already know that the side effects // UNDONE: will only be executed once because the arguments are only being pushed on the stack once. // UNDONE: In a compound equality operator on an indexer the indices are placed on the stack twice. // UNDONE: That is to say, if you have: // UNDONE: // UNDONE: C().M(z : Z(), x : X(), y : Y()) // UNDONE: // UNDONE: then we can rewrite that into // UNDONE: // UNDONE: tempc = C() // UNDONE: tempz = Z() // UNDONE: tempc.M(X(), Y(), tempz) // UNDONE: // UNDONE: See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // UNDONE: temporaries in // UNDONE: // UNDONE: C().Collection[z : Z(), x : X(), y : Y()] += 1; // UNDONE: // UNDONE: because we have to ensure not just that Z() happens first, but in additioan that X() and Y() are only // UNDONE: called once. We have to generate this as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempz = Z() // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // UNDONE: // UNDONE: Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // UNDONE: However, we can still do the optimization where constants are not stored in // UNDONE: temporaries; if we have // UNDONE: // UNDONE: C().Collection[z : 123, y : Y(), x : X()] += 1; // UNDONE: // UNDONE: Then we can generate that as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; // UNDONE: // UNDONE: For now, we'll punt on both problems, as indexers are not implemented yet anyway. // UNDONE: We'll just generate one temporary for each argument. This will work, but in the // UNDONE: subsequent rewritings will generate more unnecessary temporaries. var transformedArguments = ArrayBuilder<BoundExpression>.GetInstance(); foreach (var argument in indexer.Arguments) { var rewrittenArgument = (BoundExpression)Visit(argument); var argumentTemp = TempHelpers.StoreToTemp(rewrittenArgument, RefKind.None, containingSymbol); transformedArguments.Add(argumentTemp.Item2); stores.Add(argumentTemp.Item1); temps.Add(argumentTemp.Item2.LocalSymbol); } transformedLHS = new BoundIndexerAccess(indexer.Syntax, indexer.SyntaxTree, transformedArguments.ToReadOnlyAndFree(), transformedReceiver, indexer.IndexerSymbol, indexer.Type); } else if (node.Left.Kind == BoundKind.Local || node.Left.Kind == BoundKind.Parameter) { // No temporaries are needed. Just generate local = local + value transformedLHS = node.Left; } else if (node.Left.Kind == BoundKind.FieldAccess) { // * If the field is static then no temporaries are needed. // * If the field is not static and the receiver is of reference type then generate t = r; t.f = t.f + value // * If the field is not static and the receiver is a variable of value type then we'll fall into the // general variable case below. var fieldAccess = (BoundFieldAccess)node.Left; if (fieldAccess.ReceiverOpt == null) { transformedLHS = fieldAccess; } else if (!fieldAccess.ReceiverOpt.Type.IsValueType) { var rewrittenReceiver = (BoundExpression)Visit(fieldAccess.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundFieldAccess(fieldAccess.Syntax, fieldAccess.SyntaxTree, receiverTemp.Item2, fieldAccess.FieldSymbol, null); } } if (transformedLHS == null) { // We made no transformation above. Either we have array[index] += value or // structVariable.field += value; either way we have a potentially complicated variable- // producing expression on the left. Generate // ref temp = ref variable; temp = temp + value var rewrittenVariable = (BoundExpression)Visit(node.Left); var variableTemp = TempHelpers.StoreToTemp(rewrittenVariable, RefKind.Ref, containingSymbol); stores.Add(variableTemp.Item1); temps.Add(variableTemp.Item2.LocalSymbol); transformedLHS = variableTemp.Item2; } // OK, we now have the temporary declarations, the temporary stores, and the transformed left hand side. // We need to generate // // xlhs = (FINAL)((LEFT)xlhs op rhs) // // And then wrap it up with the generated temporaries. // // (The right hand side has already been converted to the type expected by the operator.) BoundExpression opLHS = BoundConversion.SynthesizedConversion(transformedLHS, node.LeftConversion, node.Operator.LeftType); Debug.Assert(node.Right.Type == node.Operator.RightType); BoundExpression op = new BoundBinaryOperator(null, null, node.Operator.Kind, opLHS, node.Right, null, node.Operator.ReturnType); BoundExpression opFinal = BoundConversion.SynthesizedConversion(op, node.FinalConversion, node.Left.Type); BoundExpression assignment = new BoundAssignmentOperator(null, null, transformedLHS, opFinal, node.Left.Type); // OK, at this point we have: // // * temps evaluating and storing portions of the LHS that must be evaluated only once. // * the "transformed" left hand side, rebuilt to use temps where necessary // * the assignment "xlhs = (FINAL)((LEFT)xlhs op (RIGHT)rhs)" // // Notice that we have recursively rewritten the bound nodes that are things stored in // the temps, but we might have more rewriting to do on the assignment. There are three // conversions in there that might be lowered to method calls, an operator that might // be lowered to delegate combine, string concat, and so on, and don't forget, we // haven't lowered the right hand side at all! Let's rewrite all these things at once. BoundExpression rewrittenAssignment = (BoundExpression)Visit(assignment); BoundExpression result = (temps.Count == 0) ? rewrittenAssignment : new BoundSequence(null, null, temps.ToReadOnly(), stores.ToReadOnly(), rewrittenAssignment, rewrittenAssignment.Type); temps.Free(); stores.Free(); return result; }
private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bool used) { // Avoid rewriting if node has errors since at least // one of the operands is invalid. if (node.HasErrors) { return node; } var propertyAccessor = node.Left as BoundPropertyAccess; if (propertyAccessor == null) { return (BoundExpression)base.VisitAssignmentOperator(node); } // Rewrite property assignment into call to setter. var property = propertyAccessor.PropertySymbol.GetBaseProperty(); var setMethod = property.SetMethod; Debug.Assert(setMethod != null); Debug.Assert(setMethod.Parameters.Count == 1); Debug.Assert(!setMethod.IsOverride); var rewrittenReceiver = (BoundExpression)Visit(propertyAccessor.ReceiverOpt); var rewrittenArgument = (BoundExpression)Visit(node.Right); if (used) { // Save expression value to a temporary before calling the // setter, and restore the temporary after the setter, so the // assignment can be used as an embedded expression. var exprType = rewrittenArgument.Type; var tempSymbol = new TempLocalSymbol(exprType, RefKind.None, containingSymbol); var tempLocal = new BoundLocal(null, null, tempSymbol, null, exprType); var saveTemp = new BoundAssignmentOperator( null, null, tempLocal, rewrittenArgument, exprType); var call = BoundCall.SynthesizedCall( rewrittenReceiver, setMethod, saveTemp); return new BoundSequence( node.Syntax, node.SyntaxTree, ReadOnlyArray<LocalSymbol>.CreateFrom(tempSymbol), ReadOnlyArray<BoundExpression>.CreateFrom(call), tempLocal, exprType); } else { return BoundCall.SynthesizedCall( rewrittenReceiver, setMethod, rewrittenArgument); } }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { // Assume value of expression is used. return VisitAssignmentOperator(node, used: true); }
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); }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { BoundExpression originalLeft = node.Left; if (originalLeft.Kind != BoundKind.Local) { return base.VisitAssignmentOperator(node); } var leftLocal = (BoundLocal)originalLeft; BoundExpression originalRight = node.Right; if (leftLocal.LocalSymbol.RefKind != RefKind.None && node.RefKind != RefKind.None && NeedsProxy(leftLocal.LocalSymbol)) { Debug.Assert(!proxies.ContainsKey(leftLocal.LocalSymbol)); Debug.Assert(!IsStackAlloc(originalRight)); //spilling ref local variables throw ExceptionUtilities.Unreachable; } if (NeedsProxy(leftLocal.LocalSymbol) && !proxies.ContainsKey(leftLocal.LocalSymbol)) { Debug.Assert(leftLocal.LocalSymbol.DeclarationKind == LocalDeclarationKind.None); // spilling temp variables throw ExceptionUtilities.Unreachable; } BoundExpression rewrittenLeft = (BoundExpression)this.Visit(leftLocal); BoundExpression rewrittenRight = (BoundExpression)this.Visit(originalRight); TypeSymbol rewrittenType = VisitType(node.Type); // Check if we're assigning the result of stackalloc to a hoisted local. // If we are, we need to store the result in a temp local and then assign // the value of the local to the field corresponding to the hoisted local. // If the receiver of the field is on the stack when the stackalloc happens, // popping it will free the memory (?) or otherwise cause verification issues. // DevDiv Bugs 59454 if (rewrittenLeft.Kind != BoundKind.Local && IsStackAlloc(originalRight)) { // From ILGENREC::genAssign: // DevDiv Bugs 59454: Handle hoisted local initialized with a stackalloc // NOTE: Need to check for cast of stackalloc on RHS. // If LHS isLocal, then genAddr is a noop so regular case works fine. SyntheticBoundNodeFactory factory = new SyntheticBoundNodeFactory(this.CurrentMethod, rewrittenLeft.Syntax, this.CompilationState, this.Diagnostics); BoundAssignmentOperator tempAssignment; BoundLocal tempLocal = factory.StoreToTemp(rewrittenRight, out tempAssignment); Debug.Assert(node.RefKind == RefKind.None); BoundAssignmentOperator rewrittenAssignment = node.Update(rewrittenLeft, tempLocal, node.RefKind, rewrittenType); return new BoundSequence( node.Syntax, ImmutableArray.Create<LocalSymbol>(tempLocal.LocalSymbol), ImmutableArray.Create<BoundExpression>(tempAssignment), rewrittenAssignment, rewrittenType); } return node.Update(rewrittenLeft, rewrittenRight, node.RefKind, rewrittenType); }
private bool EmitAssignmentPreamble(BoundAssignmentOperator assignmentOperator) { var assignmentTarget = assignmentOperator.Left; bool lhsUsesStack = false; switch (assignmentTarget.Kind) { case BoundKind.RefValueOperator: EmitRefValueAddress((BoundRefValueOperator)assignmentTarget); break; case BoundKind.FieldAccess: { var left = (BoundFieldAccess)assignmentTarget; if (!left.FieldSymbol.IsStatic) { var temp = EmitReceiverRef(left.ReceiverOpt); Debug.Assert(temp == null, "temp is unexpected when assigning to a field"); lhsUsesStack = true; } } break; case BoundKind.Parameter: { var left = (BoundParameter)assignmentTarget; if (left.ParameterSymbol.RefKind != RefKind.None) { _builder.EmitLoadArgumentOpcode(ParameterSlot(left)); lhsUsesStack = true; } } break; case BoundKind.Local: { var left = (BoundLocal)assignmentTarget; // Again, consider our earlier case: // // ref int addr = ref N().s; // int sum = addr + 10; // addr = sum; // // There are three different ways we could be assigning to a local. // // In the first case, we want to simply call N(), take the address // of s, and then store that address in addr. // // In the second case again we simply want to compute the sum and // store the result in sum. // // In the third case however we want to first load the contents of // addr -- the address of field s -- then put the sum on the stack, // and then do an indirect store. In that case we need to have the // contents of addr on the stack. if (left.LocalSymbol.RefKind != RefKind.None && assignmentOperator.RefKind == RefKind.None) { if (!IsStackLocal(left.LocalSymbol)) { LocalDefinition localDefinition = GetLocal(left); _builder.EmitLocalLoad(localDefinition); } else { // this is a case of indirect assignment to a stack temp. // currently byref temp can only be a stack local in scenarios where // there is only one assignment and it is the last one. // I do not yet know how to support cases where we assign more than once. // That where Dup of LHS would be needed, but as a general scenario // it is not always possible to handle. Fortunately all the cases where we // indirectly assign to a byref temp come from rewriter and all // they all are write-once cases. // // For now analyzer asserts that indirect writes are final reads of // a ref local. And we never need a dup here. // builder.EmitOpCode(ILOpCode.Dup); } lhsUsesStack = true; } } break; case BoundKind.ArrayAccess: { var left = (BoundArrayAccess)assignmentTarget; EmitExpression(left.Expression, used: true); EmitArrayIndices(left.Indices); lhsUsesStack = true; } break; case BoundKind.ThisReference: { var left = (BoundThisReference)assignmentTarget; var temp = EmitAddress(left, AddressKind.Writeable); Debug.Assert(temp == null, "taking ref of this should not create a temp"); lhsUsesStack = true; } break; case BoundKind.Dup: { var left = (BoundDup)assignmentTarget; var temp = EmitAddress(left, AddressKind.Writeable); Debug.Assert(temp == null, "taking ref of Dup should not create a temp"); lhsUsesStack = true; } break; case BoundKind.PointerIndirectionOperator: { var left = (BoundPointerIndirectionOperator)assignmentTarget; EmitExpression(left.Operand, used: true); lhsUsesStack = true; } break; case BoundKind.Sequence: { var sequence = (BoundSequence)assignmentTarget; DefineLocals(sequence); EmitSideEffects(sequence); lhsUsesStack = EmitAssignmentPreamble(assignmentOperator.Update(sequence.Value, assignmentOperator.Right, assignmentOperator.RefKind, assignmentOperator.Type)); // doNotRelease will be released in EmitStore after we are done with the whole assignment. var doNotRelease = DigForValueLocal(sequence); FreeLocals(sequence, doNotRelease); } break; case BoundKind.Call: { var left = (BoundCall)assignmentTarget; Debug.Assert(left.Method.RefKind != RefKind.None); EmitCallExpression(left, UseKind.UsedAsAddress); lhsUsesStack = true; } break; case BoundKind.PropertyAccess: case BoundKind.IndexerAccess: // Property access should have been rewritten. case BoundKind.PreviousSubmissionReference: // Script references are lowered to a this reference and a field access. throw ExceptionUtilities.UnexpectedValue(assignmentTarget.Kind); case BoundKind.PseudoVariable: EmitPseudoVariableAddress((BoundPseudoVariable)assignmentTarget); lhsUsesStack = true; break; case BoundKind.ModuleVersionId: case BoundKind.InstrumentationPayloadRoot: break; default: throw ExceptionUtilities.UnexpectedValue(assignmentTarget.Kind); } return lhsUsesStack; }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { // Assume value of expression is used. return(VisitAssignmentOperator(node, used: true)); }
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; }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { return(this.SetMayHaveSideEffects()); }
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); } }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { return this.SetMayHaveSideEffects(); }