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; }
public override object VisitIndexerAccess(BoundIndexerAccess node, object arg) { return Unimplemented(node, "indexer access"); }
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); }
public override object VisitIndexerAccess(BoundIndexerAccess node, object arg) { return(Unimplemented(node, "indexer access")); }