private BoundDynamicIndexerAccess TransformDynamicIndexerAccess(BoundDynamicIndexerAccess indexerAccess, ArrayBuilder <BoundExpression> stores, ArrayBuilder <LocalSymbol> temps) { BoundExpression loweredReceiver; if (CanChangeValueBetweenReads(indexerAccess.ReceiverOpt)) { BoundAssignmentOperator assignmentToTemp; var temp = _factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredReceiver = temp; } else { loweredReceiver = indexerAccess.ReceiverOpt; } var arguments = indexerAccess.Arguments; var loweredArguments = new BoundExpression[arguments.Length]; for (int i = 0; i < arguments.Length; i++) { if (CanChangeValueBetweenReads(arguments[i])) { BoundAssignmentOperator assignmentToTemp; var temp = _factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, indexerAccess.ArgumentRefKindsOpt.RefKinds(i) != RefKind.None ? RefKind.Ref : RefKind.None); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredArguments[i] = temp; } else { loweredArguments[i] = arguments[i]; } } return(new BoundDynamicIndexerAccess( indexerAccess.Syntax, loweredReceiver, loweredArguments.AsImmutableOrNull(), indexerAccess.ArgumentNamesOpt, indexerAccess.ArgumentRefKindsOpt, indexerAccess.ApplicableIndexers, indexerAccess.Type)); }
// Shared helper for MakeObjectCreationWithInitializer and MakeNewT private BoundExpression MakeObjectCreationWithInitializer( CSharpSyntaxNode syntax, BoundExpression rewrittenObjectCreation, BoundExpression initializerExpression, TypeSymbol type) { Debug.Assert(!inExpressionLambda); Debug.Assert(initializerExpression != null && !initializerExpression.HasErrors); // Create a temp and assign it with the object creation expression. BoundAssignmentOperator boundAssignmentToTemp; BoundLocal boundTemp = this.factory.StoreToTemp(rewrittenObjectCreation, out boundAssignmentToTemp); // Rewrite object/collection initializer expressions ArrayBuilder <BoundExpression> dynamicSiteInitializers = null; ArrayBuilder <BoundExpression> loweredInitializers = ArrayBuilder <BoundExpression> .GetInstance(); AddObjectOrCollectionInitializers(ref dynamicSiteInitializers, loweredInitializers, boundTemp, initializerExpression); int dynamicSiteCount = (dynamicSiteInitializers != null) ? dynamicSiteInitializers.Count : 0; var sideEffects = new BoundExpression[1 + dynamicSiteCount + loweredInitializers.Count]; sideEffects[0] = boundAssignmentToTemp; if (dynamicSiteCount > 0) { dynamicSiteInitializers.CopyTo(sideEffects, 1); dynamicSiteInitializers.Free(); } loweredInitializers.CopyTo(sideEffects, 1 + dynamicSiteCount); loweredInitializers.Free(); return(new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: sideEffects.AsImmutableOrNull(), value: boundTemp, type: type)); }
internal static ImmutableArray <BoundExpression> GetCallSiteArguments(BoundExpression callSiteFieldAccess, BoundExpression receiver, ImmutableArray <BoundExpression> arguments, BoundExpression right) { var result = new BoundExpression[1 + (receiver != null ? 1 : 0) + arguments.Length + (right != null ? 1 : 0)]; int j = 0; result[j++] = callSiteFieldAccess; if (receiver != null) { result[j++] = receiver; } arguments.CopyTo(result, j); j += arguments.Length; if (right != null) { result[j++] = right; } return(result.AsImmutableOrNull()); }
private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAccess, ArrayBuilder <BoundExpression> stores, ArrayBuilder <LocalSymbol> temps) { var receiverOpt = indexerAccess.ReceiverOpt; Debug.Assert(receiverOpt != null); BoundExpression transformedReceiver; if (CanChangeValueBetweenReads(receiverOpt)) { BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); BoundAssignmentOperator assignmentToTemp; // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order), // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs. // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp) // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not, // SPEC VIOLATION: as value types. var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter; var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None); transformedReceiver = receiverTemp; stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); } else { transformedReceiver = VisitExpression(receiverOpt); } // Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // we have to preserve both the source-code order of the side effects and the side effects // only being executed once. // // This is a subtly different problem than the problem faced by the conventional call // rewriter; with the conventional call rewriter we already know that the side effects // will only be executed once because the arguments are only being pushed on the stack once. // In a compound equality operator on an indexer the indices are placed on the stack twice. // That is to say, if you have: // // C().M(z : Z(), x : X(), y : Y()) // // then we can rewrite that into // // tempc = C() // tempz = Z() // tempc.M(X(), Y(), tempz) // // See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // temporaries in // // C().Collection[z : Z(), x : X(), y : Y()] += 1; // // because we have to ensure not just that Z() happens first, but in addition that X() and Y() are only // called once. We have to generate this as // // tempc = C().Collection // tempz = Z() // tempx = X() // tempy = Y() // tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // // Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // However, we can still do the optimization where constants are not stored in // temporaries; if we have // // C().Collection[z : 123, y : Y(), x : X()] += 1; // // Then we can generate that as // // tempc = C().Collection // tempx = X() // tempy = Y() // tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; ImmutableArray <BoundExpression> rewrittenArguments = VisitList(indexerAccess.Arguments); SyntaxNode syntax = indexerAccess.Syntax; PropertySymbol indexer = indexerAccess.Indexer; ImmutableArray <RefKind> argumentRefKinds = indexerAccess.ArgumentRefKindsOpt; bool expanded = indexerAccess.Expanded; ImmutableArray <int> argsToParamsOpt = indexerAccess.ArgsToParamsOpt; ImmutableArray <ParameterSymbol> parameters = indexer.Parameters; BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder <BoundAssignmentOperator> storesToTemps = ArrayBuilder <BoundAssignmentOperator> .GetInstance(rewrittenArguments.Length); ArrayBuilder <RefKind> refKinds = ArrayBuilder <RefKind> .GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { BoundExpression array = BuildParamsArray(syntax, indexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); BoundAssignmentOperator storeToTemp; var boundTemp = _factory.StoreToTemp(array, out storeToTemp); stores.Add(storeToTemp); temps.Add(boundTemp.LocalSymbol); actualArguments[actualArguments.Length - 1] = boundTemp; } // Step three: Now fill in the optional arguments. (Dev11 uses the getter for optional arguments in // compound assignments, but for deconstructions we use the setter if the getter is missing.) var accessor = indexer.GetOwnOrInheritedGetMethod() ?? indexer.GetOwnOrInheritedSetMethod(); InsertMissingOptionalArguments(syntax, accessor.Parameters, actualArguments); // For a call, step four would be to optimize away some of the temps. However, we need them all to prevent // duplicate side-effects, so we'll skip that step. if (indexer.ContainingType.IsComImport) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps); } rewrittenArguments = actualArguments.AsImmutableOrNull(); foreach (BoundAssignmentOperator tempAssignment in storesToTemps) { temps.Add(((BoundLocal)tempAssignment.Left).LocalSymbol); stores.Add(tempAssignment); } storesToTemps.Free(); argumentRefKinds = GetRefKindsOrNull(refKinds); refKinds.Free(); // This is a temporary object that will be rewritten away before the lowering completes. return(new BoundIndexerAccess( syntax, transformedReceiver, indexer, rewrittenArguments, default(ImmutableArray <string>), argumentRefKinds, false, default(ImmutableArray <int>), null, indexerAccess.UseSetterForDefaultArgumentGeneration, indexerAccess.Type)); }
public BoundExpression Array(TypeSymbol elementType, BoundExpression[] elements) { return Array(elementType, elements.AsImmutableOrNull()); }
public BoundExpression Sequence(BoundExpression[] sideEffects, BoundExpression result, TypeSymbol type = null) { return new BoundSequence(Syntax, ImmutableArray<LocalSymbol>.Empty, sideEffects.AsImmutableOrNull(), result, type ?? result.Type) { WasCompilerGenerated = true }; }
internal static ImmutableArray<BoundExpression> GetCallSiteArguments(BoundExpression callSiteFieldAccess, BoundExpression receiver, ImmutableArray<BoundExpression> arguments, BoundExpression right) { var result = new BoundExpression[1 + (receiver != null ? 1 : 0) + arguments.Length + (right != null ? 1 : 0)]; int j = 0; result[j++] = callSiteFieldAccess; if (receiver != null) { result[j++] = receiver; } arguments.CopyTo(result, j); j += arguments.Length; if (right != null) { result[j++] = right; } return result.AsImmutableOrNull(); }
private BoundExpression MakeBinderConstruction(WellKnownMember factoryMethod, BoundExpression[] args) { var binderFactory = _factory.WellKnownMember(factoryMethod); if ((object)binderFactory == null) { return null; } return _factory.Call(null, (MethodSymbol)binderFactory, args.AsImmutableOrNull()); }
/// <summary> /// Process tempStores and add them as sideeffects to arguments where needed. The return /// value tells how many temps are actually needed. For unnecessary temps the corresponding /// temp store will be cleared. /// </summary> private static int MergeArgumentsAndSideEffects( BoundExpression[] arguments, ArrayBuilder <RefKind> refKinds, ArrayBuilder <BoundAssignmentOperator> tempStores) { Debug.Assert(arguments != null); Debug.Assert(refKinds != null); Debug.Assert(tempStores != null); int tempsRemainedInUse = tempStores.Count; // Suppose we've got temporaries: t0 = A(), t1 = B(), t2 = C(), t4 = D(), t5 = E() // and arguments: t0, t2, t1, t4, 10, t5 // // We wish to produce arguments list: A(), SEQ(t1=B(), C()), t1, D(), 10, E() // // Our algorithm essentially finds temp stores that must happen before given argument // load, and if there are any they become side effects of the given load. // Stores immediately followed by loads of the same thing can be eliminated. // // Constraints: // Stores must happen before corresponding loads. // Stores cannot move relative to other stores. If arg was movable it would not need a temp. int firstUnclaimedStore = 0; for (int a = 0; a < arguments.Length; ++a) { var argument = arguments[a]; // if argument is a load, search for corresponding store. if store is found, extract // the actual expression we were storing and add it as an argument - this one does // not need a temp. if there are any unclaimed stores before the found one, add them // as side effects that precede this arg, they cannot happen later. if (argument.Kind == BoundKind.Local) { var correspondingStore = -1; for (int i = firstUnclaimedStore; i < tempStores.Count; i++) { if (tempStores[i].Left == argument) { correspondingStore = i; break; } } // store found? if (correspondingStore != -1) { var value = tempStores[correspondingStore].Right; // When we created the temp, we dropped the argument RefKind // since the local contained its own RefKind. Since we're removing // the temp, the argument RefKind needs to be restored. refKinds[a] = ((BoundLocal)argument).LocalSymbol.RefKind; // the matched store will not need to go into sideffects, only ones before it will // remove the store to signal that we are not using its temp. tempStores[correspondingStore] = null; tempsRemainedInUse--; // no need for sideeffects? // just combine store and load if (correspondingStore == firstUnclaimedStore) { arguments[a] = value; } else { var sideffects = new BoundExpression[correspondingStore - firstUnclaimedStore]; for (int s = 0; s < sideffects.Length; s++) { sideffects[s] = tempStores[firstUnclaimedStore + s]; } arguments[a] = new BoundSequence( value.Syntax, // this sequence does not own locals. Note that temps that // we use for the rewrite are stored in one arg and loaded // in another so they must live in a scope above. ImmutableArray <LocalSymbol> .Empty, sideffects.AsImmutableOrNull(), value, value.Type); } firstUnclaimedStore = correspondingStore + 1; } } } Debug.Assert(firstUnclaimedStore == tempStores.Count, "not all sideeffects were claimed"); return(tempsRemainedInUse); }
private BoundExpression BindAnonymousObjectCreation(AnonymousObjectCreationExpressionSyntax node, DiagnosticBag diagnostics) { // prepare var initializers = node.Initializers; int fieldCount = initializers.Count; bool hasError = false; // bind field initializers BoundExpression[] boundExpressions = new BoundExpression[fieldCount]; AnonymousTypeField[] fields = new AnonymousTypeField[fieldCount]; CSharpSyntaxNode[] fieldSyntaxNodes = new CSharpSyntaxNode[fieldCount]; // WARNING: Note that SemanticModel.GetDeclaredSymbol for field initializer node relies on // the fact that the order of properties in anonymous type template corresponds // 1-to-1 to the appropriate filed initializer syntax nodes; This means such // correspondence must be preserved all the time including erroneos scenarios // set of names already used HashSet<string> uniqueFieldNames = new HashSet<string>(); for (int i = 0; i < fieldCount; i++) { AnonymousObjectMemberDeclaratorSyntax fieldInitializer = initializers[i]; NameEqualsSyntax nameEquals = fieldInitializer.NameEquals; ExpressionSyntax expression = fieldInitializer.Expression; SyntaxToken nameToken = default(SyntaxToken); if (nameEquals != null) { nameToken = nameEquals.Name.Identifier; } else { nameToken = expression.ExtractAnonymousTypeMemberName(); } hasError = hasError || expression.HasErrors; boundExpressions[i] = this.BindValue(expression, diagnostics, BindValueKind.RValue); // check the name to be unique string fieldName = null; if (nameToken.Kind() == SyntaxKind.IdentifierToken) { fieldName = nameToken.ValueText; if (uniqueFieldNames.Contains(fieldName)) { // name duplication Error(diagnostics, ErrorCode.ERR_AnonymousTypeDuplicatePropertyName, fieldInitializer); hasError = true; fieldName = null; } else { uniqueFieldNames.Add(fieldName); } } else { // there is something wrong with field's name hasError = true; } // calculate the expression's type and report errors if needed TypeSymbol fieldType = GetAnonymousTypeFieldType(boundExpressions[i], fieldInitializer, diagnostics, ref hasError); // build anonymous type field descriptor fieldSyntaxNodes[i] = (nameToken.Kind() == SyntaxKind.IdentifierToken) ? (CSharpSyntaxNode)nameToken.Parent : fieldInitializer; fields[i] = new AnonymousTypeField(fieldName == null ? "$" + i.ToString() : fieldName, fieldSyntaxNodes[i].Location, fieldType); // NOTE: ERR_InvalidAnonymousTypeMemberDeclarator (CS0746) would be generated by parser if needed } // Create anonymous type AnonymousTypeManager manager = this.Compilation.AnonymousTypeManager; AnonymousTypeDescriptor descriptor = new AnonymousTypeDescriptor(fields.AsImmutableOrNull(), node.NewKeyword.GetLocation()); NamedTypeSymbol anonymousType = manager.ConstructAnonymousTypeSymbol(descriptor); // declarators - bound nodes created for providing semantic info // on anonymous type fields having explicitly specified name ArrayBuilder<BoundAnonymousPropertyDeclaration> declarators = ArrayBuilder<BoundAnonymousPropertyDeclaration>.GetInstance(); for (int i = 0; i < fieldCount; i++) { NameEqualsSyntax explicitName = initializers[i].NameEquals; if (explicitName != null) { AnonymousTypeField field = fields[i]; if (field.Name != null) { // get property symbol and create a bound property declaration node foreach (var symbol in anonymousType.GetMembers(field.Name)) { if (symbol.Kind == SymbolKind.Property) { declarators.Add(new BoundAnonymousPropertyDeclaration(fieldSyntaxNodes[i], (PropertySymbol)symbol, field.Type)); break; } } } } } // check if anonymous object creation is allowed in this context if (!this.IsAnonymousTypesAllowed()) { Error(diagnostics, ErrorCode.ERR_AnonymousTypeNotAvailable, node.NewKeyword); hasError = true; } // Finally create a bound node return new BoundAnonymousObjectCreationExpression( node, anonymousType.InstanceConstructors[0], boundExpressions.AsImmutableOrNull(), declarators.ToImmutableAndFree(), anonymousType, hasError); }
public ImmutableArray<BoundExpression> TypeOfs(ImmutableArray<TypeSymbol> typeArguments) { var result = new BoundExpression[typeArguments.Length]; for (int i = 0; i < typeArguments.Length; i++) { result[i] = Typeof(typeArguments[i]); } return result.AsImmutableOrNull(); }
private BoundDynamicIndexerAccess TransformDynamicIndexerAccess(BoundDynamicIndexerAccess indexerAccess, ArrayBuilder<BoundExpression> stores, ArrayBuilder<LocalSymbol> temps) { BoundExpression loweredReceiver; if (CanChangeValueBetweenReads(indexerAccess.ReceiverOpt)) { BoundAssignmentOperator assignmentToTemp; var temp = _factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredReceiver = temp; } else { loweredReceiver = indexerAccess.ReceiverOpt; } var arguments = indexerAccess.Arguments; var loweredArguments = new BoundExpression[arguments.Length]; for (int i = 0; i < arguments.Length; i++) { if (CanChangeValueBetweenReads(arguments[i])) { BoundAssignmentOperator assignmentToTemp; var temp = _factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, indexerAccess.ArgumentRefKindsOpt.RefKinds(i) != RefKind.None ? RefKind.Ref : RefKind.None); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredArguments[i] = temp; } else { loweredArguments[i] = arguments[i]; } } return new BoundDynamicIndexerAccess( indexerAccess.Syntax, loweredReceiver, loweredArguments.AsImmutableOrNull(), indexerAccess.ArgumentNamesOpt, indexerAccess.ArgumentRefKindsOpt, indexerAccess.ApplicableIndexers, indexerAccess.Type); }
private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAccess, ArrayBuilder<BoundExpression> stores, ArrayBuilder<LocalSymbol> temps) { var receiverOpt = indexerAccess.ReceiverOpt; Debug.Assert(receiverOpt != null); BoundExpression transformedReceiver; if (CanChangeValueBetweenReads(receiverOpt)) { BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); BoundAssignmentOperator assignmentToTemp; // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order), // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs. // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp) // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not, // SPEC VIOLATION: as value types. var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter; var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None); transformedReceiver = receiverTemp; stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); } else { transformedReceiver = VisitExpression(receiverOpt); } // Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // we have to preserve both the source-code order of the side effects and the side effects // only being executed once. // // This is a subtly different problem than the problem faced by the conventional call // rewriter; with the conventional call rewriter we already know that the side effects // will only be executed once because the arguments are only being pushed on the stack once. // In a compound equality operator on an indexer the indices are placed on the stack twice. // That is to say, if you have: // // C().M(z : Z(), x : X(), y : Y()) // // then we can rewrite that into // // tempc = C() // tempz = Z() // tempc.M(X(), Y(), tempz) // // See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // temporaries in // // C().Collection[z : Z(), x : X(), y : Y()] += 1; // // because we have to ensure not just that Z() happens first, but in addition that X() and Y() are only // called once. We have to generate this as // // tempc = C().Collection // tempz = Z() // tempx = X() // tempy = Y() // tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // // Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // However, we can still do the optimization where constants are not stored in // temporaries; if we have // // C().Collection[z : 123, y : Y(), x : X()] += 1; // // Then we can generate that as // // tempc = C().Collection // tempx = X() // tempy = Y() // tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; ImmutableArray<BoundExpression> rewrittenArguments = VisitList(indexerAccess.Arguments); SyntaxNode syntax = indexerAccess.Syntax; PropertySymbol indexer = indexerAccess.Indexer; ImmutableArray<RefKind> argumentRefKinds = indexerAccess.ArgumentRefKindsOpt; bool expanded = indexerAccess.Expanded; ImmutableArray<int> argsToParamsOpt = indexerAccess.ArgsToParamsOpt; ImmutableArray<ParameterSymbol> parameters = indexer.Parameters; BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder<BoundAssignmentOperator> storesToTemps = ArrayBuilder<BoundAssignmentOperator>.GetInstance(rewrittenArguments.Length); ArrayBuilder<RefKind> refKinds = ArrayBuilder<RefKind>.GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { BoundExpression array = BuildParamsArray(syntax, indexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); BoundAssignmentOperator storeToTemp; var boundTemp = _factory.StoreToTemp(array, out storeToTemp); stores.Add(storeToTemp); temps.Add(boundTemp.LocalSymbol); actualArguments[actualArguments.Length - 1] = boundTemp; } // Step three: Now fill in the optional arguments. (Dev11 uses the // getter for optional arguments in compound assignments.) var getMethod = indexer.GetOwnOrInheritedGetMethod(); Debug.Assert((object)getMethod != null); InsertMissingOptionalArguments(syntax, getMethod.Parameters, actualArguments); // For a call, step four would be to optimize away some of the temps. However, we need them all to prevent // duplicate side-effects, so we'll skip that step. if (indexer.ContainingType.IsComImport) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps); } rewrittenArguments = actualArguments.AsImmutableOrNull(); foreach (BoundAssignmentOperator tempAssignment in storesToTemps) { temps.Add(((BoundLocal)tempAssignment.Left).LocalSymbol); stores.Add(tempAssignment); } storesToTemps.Free(); argumentRefKinds = GetRefKindsOrNull(refKinds); refKinds.Free(); // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes. // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of indexers. return new BoundIndexerAccess( syntax, transformedReceiver, indexer, rewrittenArguments, default(ImmutableArray<string>), argumentRefKinds, false, default(ImmutableArray<int>), indexerAccess.Type); }
/// <summary> /// In the expanded form of a compound assignment (or increment/decrement), the LHS appears multiple times. /// If we aren't careful, this can result in repeated side-effects. This creates (ordered) temps for all of the /// subexpressions that could result in side-effects and returns a side-effect-free expression that can be used /// in place of the LHS in the expanded form. /// </summary> /// <param name="originalLHS">The LHS sub-expression of the compound assignment (or increment/decrement).</param> /// <param name="stores">Populated with a list of assignment expressions that initialize the temporary locals.</param> /// <param name="temps">Populated with a list of temporary local symbols.</param> /// <param name="isDynamicAssignment">True if the compound assignment is a dynamic operation.</param> /// <returns> /// A side-effect-free expression representing the LHS. /// The returned node needs to be lowered but its children are already lowered. /// </returns> private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalLHS, ArrayBuilder <BoundExpression> stores, ArrayBuilder <LocalSymbol> temps, bool isDynamicAssignment) { // 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 switch (originalLHS.Kind) { case 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)originalLHS; // If the property is static or if the receiver is of kind "Base" or "this", then we can just generate prop = prop + value if (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !IntroducingReadCanBeObservable(prop.ReceiverOpt)) { return(prop); } Debug.Assert(prop.ReceiverOpt.Kind != BoundKind.TypeExpression); BoundExpression rewrittenReceiver = VisitExpression(prop.ReceiverOpt); BoundAssignmentOperator assignmentToTemp; // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order), // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs. // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp) // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not, // SPEC VIOLATION: as value types. var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter; var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None); stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes. // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of properties. return(new BoundPropertyAccess(prop.Syntax, receiverTemp, prop.PropertySymbol, prop.ResultKind, prop.Type)); } case BoundKind.DynamicMemberAccess: { var memberAccess = (BoundDynamicMemberAccess)originalLHS; if (!IntroducingReadCanBeObservable(memberAccess.Receiver)) { return(memberAccess); } // store receiver to temp: var rewrittenReceiver = VisitExpression(memberAccess.Receiver); BoundAssignmentOperator assignmentToTemp; var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); return(new BoundDynamicMemberAccess(memberAccess.Syntax, receiverTemp, memberAccess.TypeArgumentsOpt, memberAccess.Name, memberAccess.Invoked, memberAccess.Indexed, memberAccess.Type)); } case BoundKind.IndexerAccess: { BoundIndexerAccess indexerAccess = (BoundIndexerAccess)originalLHS; var receiverOpt = indexerAccess.ReceiverOpt; Debug.Assert(receiverOpt != null); BoundExpression transformedReceiver; if (IntroducingReadCanBeObservable(receiverOpt)) { BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); BoundAssignmentOperator assignmentToTemp; // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order), // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs. // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp) // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not, // SPEC VIOLATION: as value types. var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter; var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None); transformedReceiver = receiverTemp; stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); } else { transformedReceiver = VisitExpression(receiverOpt); } // Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // we have to preserve both the source-code order of the side effects and the side effects // only being executed once. // // This is a subtly different problem than the problem faced by the conventional call // rewriter; with the conventional call rewriter we already know that the side effects // will only be executed once because the arguments are only being pushed on the stack once. // In a compound equality operator on an indexer the indices are placed on the stack twice. // That is to say, if you have: // // C().M(z : Z(), x : X(), y : Y()) // // then we can rewrite that into // // tempc = C() // tempz = Z() // tempc.M(X(), Y(), tempz) // // See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // temporaries in // // C().Collection[z : Z(), x : X(), y : Y()] += 1; // // because we have to ensure not just that Z() happens first, but in addition that X() and Y() are only // called once. We have to generate this as // // tempc = C().Collection // tempz = Z() // tempx = X() // tempy = Y() // tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // // Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // However, we can still do the optimization where constants are not stored in // temporaries; if we have // // C().Collection[z : 123, y : Y(), x : X()] += 1; // // Then we can generate that as // // tempc = C().Collection // tempx = X() // tempy = Y() // tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; ImmutableArray <BoundExpression> rewrittenArguments = VisitList(indexerAccess.Arguments); CSharpSyntaxNode syntax = indexerAccess.Syntax; PropertySymbol indexer = indexerAccess.Indexer; ImmutableArray <RefKind> argumentRefKinds = indexerAccess.ArgumentRefKindsOpt; bool expanded = indexerAccess.Expanded; ImmutableArray <int> argsToParamsOpt = indexerAccess.ArgsToParamsOpt; ImmutableArray <ParameterSymbol> parameters = indexer.Parameters; BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder <BoundAssignmentOperator> storesToTemps = ArrayBuilder <BoundAssignmentOperator> .GetInstance(rewrittenArguments.Length); ArrayBuilder <RefKind> refKinds = ArrayBuilder <RefKind> .GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { BoundExpression array = BuildParamsArray(syntax, indexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); BoundAssignmentOperator storeToTemp; var boundTemp = _factory.StoreToTemp(array, out storeToTemp); stores.Add(storeToTemp); temps.Add(boundTemp.LocalSymbol); actualArguments[actualArguments.Length - 1] = boundTemp; } // Step three: Now fill in the optional arguments. (Dev11 uses the // getter for optional arguments in compound assignments.) var getMethod = indexer.GetOwnOrInheritedGetMethod(); Debug.Assert((object)getMethod != null); InsertMissingOptionalArguments(syntax, getMethod.Parameters, actualArguments); // For a call, step four would be to optimize away some of the temps. However, we need them all to prevent // duplicate side-effects, so we'll skip that step. if (indexer.ContainingType.IsComImport) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps); } rewrittenArguments = actualArguments.AsImmutableOrNull(); foreach (BoundAssignmentOperator tempAssignment in storesToTemps) { temps.Add(((BoundLocal)tempAssignment.Left).LocalSymbol); stores.Add(tempAssignment); } storesToTemps.Free(); argumentRefKinds = GetRefKindsOrNull(refKinds); refKinds.Free(); // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes. // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of indexers. return(new BoundIndexerAccess( syntax, transformedReceiver, indexer, rewrittenArguments, default(ImmutableArray <string>), argumentRefKinds, false, default(ImmutableArray <int>), indexerAccess.Type)); } case BoundKind.Local: case BoundKind.Parameter: case BoundKind.ThisReference: // a special kind of parameter // No temporaries are needed. Just generate local = local + value return(originalLHS); case 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)originalLHS; BoundExpression receiverOpt = fieldAccess.ReceiverOpt; //If the receiver is static or is the receiver is of kind "Base" or "this", then we can just generate field = field + value if (fieldAccess.FieldSymbol.IsStatic || !IntroducingReadCanBeObservable(receiverOpt)) { return(fieldAccess); } if (receiverOpt.Type.IsReferenceType) { Debug.Assert(receiverOpt.Kind != BoundKind.TypeExpression); BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); if (rewrittenReceiver.Type.IsTypeParameter()) { var memberContainingType = fieldAccess.FieldSymbol.ContainingType; // From the verifier prospective type parameters do not contain fields or methods. // the instance must be "boxed" to access the field // It makes sense to box receiver before storing into a temp - no need to box twice. rewrittenReceiver = BoxReceiver(rewrittenReceiver, memberContainingType); } BoundAssignmentOperator assignmentToTemp; var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); return(new BoundFieldAccess(fieldAccess.Syntax, receiverTemp, fieldAccess.FieldSymbol, null)); } break; } case BoundKind.DynamicIndexerAccess: { var indexerAccess = (BoundDynamicIndexerAccess)originalLHS; BoundExpression loweredReceiver; if (IntroducingReadCanBeObservable(indexerAccess.ReceiverOpt)) { BoundAssignmentOperator assignmentToTemp; var temp = _factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredReceiver = temp; } else { loweredReceiver = indexerAccess.ReceiverOpt; } var arguments = indexerAccess.Arguments; var loweredArguments = new BoundExpression[arguments.Length]; for (int i = 0; i < arguments.Length; i++) { if (IntroducingReadCanBeObservable(arguments[i])) { BoundAssignmentOperator assignmentToTemp; var temp = _factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, refKind: indexerAccess.ArgumentRefKindsOpt.RefKinds(i)); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredArguments[i] = temp; } else { loweredArguments[i] = arguments[i]; } } return(new BoundDynamicIndexerAccess( indexerAccess.Syntax, loweredReceiver, loweredArguments.AsImmutableOrNull(), indexerAccess.ArgumentNamesOpt, indexerAccess.ArgumentRefKindsOpt, indexerAccess.ApplicableIndexers, indexerAccess.Type)); } case BoundKind.ArrayAccess: if (isDynamicAssignment) { // In non-dynamic array[index] op= R we emit: // T& tmp = &array[index]; // *tmp = *L op R; // where T is the type of L. // // If L is an array access, the assignment is dynamic, the compile-time of the array is dynamic[] // and the runtime type of the array is not object[] (but e.g. string[]) the pointer approach is broken. // T is Object in such case and we can't take a read-write pointer of type Object& to an array element of non-object type. // // In this case we rewrite the assignment as follows: // // E t_array = array; // I t_index = index; (possibly more indices) // T value = t_array[t_index]; // t_array[t_index] = value op R; var arrayAccess = (BoundArrayAccess)originalLHS; var loweredArray = VisitExpression(arrayAccess.Expression); var loweredIndices = VisitList(arrayAccess.Indices); return(SpillArrayElementAccess(loweredArray, loweredIndices, stores, temps)); } break; case BoundKind.PointerElementAccess: case BoundKind.PointerIndirectionOperator: case BoundKind.RefValueOperator: break; default: throw ExceptionUtilities.UnexpectedValue(originalLHS.Kind); } // 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 // Rewrite the variable. Here we depend on the fact that the only forms // rewritten here are rewritten the same for lvalues and rvalues. BoundExpression rewrittenVariable = VisitExpression(originalLHS); BoundAssignmentOperator assignmentToTemp2; var variableTemp = _factory.StoreToTemp(rewrittenVariable, out assignmentToTemp2, refKind: RefKind.Ref); stores.Add(assignmentToTemp2); temps.Add(variableTemp.LocalSymbol); return(variableTemp); }
// Shared helper for MakeObjectCreationWithInitializer and MakeNewT private BoundExpression MakeObjectCreationWithInitializer( CSharpSyntaxNode syntax, BoundExpression rewrittenObjectCreation, BoundExpression initializerExpression, TypeSymbol type) { Debug.Assert(!inExpressionLambda); Debug.Assert(initializerExpression != null && !initializerExpression.HasErrors); // Create a temp and assign it with the object creation expression. BoundAssignmentOperator boundAssignmentToTemp; BoundLocal boundTemp = this.factory.StoreToTemp(rewrittenObjectCreation, out boundAssignmentToTemp); // Rewrite object/collection initializer expressions ArrayBuilder<BoundExpression> dynamicSiteInitializers = null; ArrayBuilder<BoundExpression> loweredInitializers = ArrayBuilder<BoundExpression>.GetInstance(); AddObjectOrCollectionInitializers(ref dynamicSiteInitializers, loweredInitializers, boundTemp, initializerExpression); int dynamicSiteCount = (dynamicSiteInitializers != null) ? dynamicSiteInitializers.Count : 0; var sideEffects = new BoundExpression[1 + dynamicSiteCount + loweredInitializers.Count]; sideEffects[0] = boundAssignmentToTemp; if (dynamicSiteCount > 0) { dynamicSiteInitializers.CopyTo(sideEffects, 1); dynamicSiteInitializers.Free(); } loweredInitializers.CopyTo(sideEffects, 1 + dynamicSiteCount); loweredInitializers.Free(); return new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: sideEffects.AsImmutableOrNull(), value: boundTemp, type: type); }
/// <summary> /// Rewrites arguments of an invocation according to the receiving method or indexer. /// It is assumed that each argument has already been lowered, but we may need /// additional rewriting for the arguments, such as generating a params array, re-ordering /// arguments based on <paramref name="argsToParamsOpt"/> map, inserting arguments for optional parameters, etc. /// <paramref name="optionalParametersMethod"/> is the method used for values of any optional parameters. /// For indexers, this method must be an accessor, and for methods it must be the method /// itself. <paramref name="optionalParametersMethod"/> is needed for indexers since getter and setter /// may have distinct optional parameter values. /// </summary> private ImmutableArray<BoundExpression> MakeArguments( SyntaxNode syntax, ImmutableArray<BoundExpression> rewrittenArguments, Symbol methodOrIndexer, MethodSymbol optionalParametersMethod, bool expanded, ImmutableArray<int> argsToParamsOpt, ref ImmutableArray<RefKind> argumentRefKindsOpt, out ImmutableArray<LocalSymbol> temps, bool invokedAsExtensionMethod = false, ThreeState enableCallerInfo = ThreeState.Unknown) { // Either the methodOrIndexer is a property, in which case the method used // for optional parameters is an accessor of that property (or an overridden // property), or the methodOrIndexer is used for optional parameters directly. Debug.Assert(((methodOrIndexer.Kind == SymbolKind.Property) && optionalParametersMethod.IsAccessor()) || ReferenceEquals(methodOrIndexer, optionalParametersMethod)); // We need to do a fancy rewrite under the following circumstances: // (1) a params array is being used; we need to generate the array. // (2) there were named arguments that reordered the arguments; we might // have to generate temporaries to ensure that the arguments are // evaluated in source code order, not the actual call order. // (3) there were optional parameters that had no corresponding arguments. // // If none of those are the case then we can just take an early out. // An applicable "vararg" method could not possibly be applicable in its expanded // form, and cannot possibly have named arguments or used optional parameters, // because the __arglist() argument has to be positional and in the last position. if (methodOrIndexer.GetIsVararg()) { Debug.Assert(rewrittenArguments.Length == methodOrIndexer.GetParameterCount() + 1); Debug.Assert(argsToParamsOpt.IsDefault); Debug.Assert(!expanded); temps = default(ImmutableArray<LocalSymbol>); return rewrittenArguments; } var receiverNamedType = invokedAsExtensionMethod ? ((MethodSymbol)methodOrIndexer).Parameters[0].Type as NamedTypeSymbol : methodOrIndexer.ContainingType; bool isComReceiver = (object)receiverNamedType != null && receiverNamedType.IsComImport; if (rewrittenArguments.Length == methodOrIndexer.GetParameterCount() && argsToParamsOpt.IsDefault && !expanded && !isComReceiver) { temps = default(ImmutableArray<LocalSymbol>); return rewrittenArguments; } // We have: // * a list of arguments, already converted to their proper types, // in source code order. Some optional arguments might be missing. // * a map showing which parameter each argument corresponds to. If // this is null, then the argument to parameter mapping is one-to-one. // * the ref kind of each argument, in source code order. That is, whether // the argument was marked as ref, out, or value (neither). // * a method symbol. // * whether the call is expanded or normal form. // We rewrite the call so that: // * if in its expanded form, we create the params array. // * if the call requires reordering of arguments because of named arguments, temporaries are generated as needed // Doing this transformation can move around refness in interesting ways. For example, consider // // A().M(y : ref B()[C()], x : out D()); // // This will be created as a call with receiver A(), symbol M, argument list ( B()[C()], D() ), // name list ( y, x ) and ref list ( ref, out ). We can rewrite this into temporaries: // // A().M( // seq ( ref int temp_y = ref B()[C()], out D() ), // temp_y ); // // Now we have a call with receiver A(), symbol M, argument list as shown, no name list, // and ref list ( out, value ). We do not want to pass a *ref* to temp_y; the temporary // storage is not the thing being ref'd! We want to pass the *value* of temp_y, which // *contains* a reference. // We attempt to minimize the number of temporaries required. Arguments which neither // produce nor observe a side effect can be placed into their proper position without // recourse to a temporary. For example: // // Where(predicate: x=>x.Length!=0, sequence: S()) // // can be rewritten without any temporaries because the conversion from lambda to // delegate does not produce any side effect that could be observed by S(). // // By contrast: // // Foo(z: this.p, y: this.Q(), x: (object)10) // // The boxing of 10 can be reordered, but the fetch of this.p has to happen before the // call to this.Q() because the call could change the value of this.p. // // We start by binding everything that is not obviously reorderable as a temporary, and // then run an optimizer to remove unnecessary temporaries. ImmutableArray<ParameterSymbol> parameters = methodOrIndexer.GetParameters(); BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder<BoundAssignmentOperator> storesToTemps = ArrayBuilder<BoundAssignmentOperator>.GetInstance(rewrittenArguments.Length); ArrayBuilder<RefKind> refKinds = ArrayBuilder<RefKind>.GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKindsOpt, rewrittenArguments, actualArguments, refKinds, storesToTemps); // all the formal arguments, except missing optionals, are now in place. // Optimize away unnecessary temporaries. // Necessary temporaries have their store instructions merged into the appropriate // argument expression. ArrayBuilder<LocalSymbol> temporariesBuilder = ArrayBuilder<LocalSymbol>.GetInstance(); OptimizeTemporaries(actualArguments, refKinds, storesToTemps, temporariesBuilder); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { actualArguments[actualArguments.Length - 1] = BuildParamsArray(syntax, methodOrIndexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); } // Step three: Now fill in the optional arguments. InsertMissingOptionalArguments(syntax, optionalParametersMethod.Parameters, actualArguments, enableCallerInfo); if (isComReceiver) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temporariesBuilder); } temps = temporariesBuilder.ToImmutableAndFree(); storesToTemps.Free(); // * The refkind map is now filled out to match the arguments. // * The list of parameter names is now null because the arguments have been reordered. // * The args-to-params map is now null because every argument exactly matches its parameter. // * The call is no longer in its expanded form. argumentRefKindsOpt = GetRefKindsOrNull(refKinds); refKinds.Free(); return actualArguments.AsImmutableOrNull(); }
/// <summary> /// In the expanded form of a compound assignment (or increment/decrement), the LHS appears multiple times. /// If we aren't careful, this can result in repeated side-effects. This creates (ordered) temps for all of the /// subexpressions that could result in side-effects and returns a side-effect-free expression that can be used /// in place of the LHS in the expanded form. /// </summary> /// <param name="originalLHS">The LHS sub-expression of the compound assignment (or increment/decrement).</param> /// <param name="stores">Populated with a list of assignment expressions that initialize the temporary locals.</param> /// <param name="temps">Populated with a list of temporary local symbols.</param> /// <param name="isDynamicAssignment">True if the compound assignment is a dynamic operation.</param> /// <returns> /// A side-effect-free expression representing the LHS. /// The returned node needs to be lowered but its children are already lowered. /// </returns> private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalLHS, ArrayBuilder<BoundExpression> stores, ArrayBuilder<LocalSymbol> temps, bool isDynamicAssignment) { // 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 switch (originalLHS.Kind) { case 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)originalLHS; // If the property is static or is the receiver is of kind "Base" or "this", then we can just generate prop = prop + value if (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !NeedsTemp(prop.ReceiverOpt)) { return prop; } Debug.Assert(prop.ReceiverOpt.Kind != BoundKind.TypeExpression); // 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 we know of is 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. BoundExpression rewrittenReceiver = VisitExpression(prop.ReceiverOpt); if (rewrittenReceiver.Type.IsTypeParameter() && rewrittenReceiver.Type.IsReferenceType) { var memberContainingType = prop.PropertySymbol.ContainingType; // From the verifier prospective type parameters do not contain fields or methods. // the instance must be boxed/constrained to access the member even if known to be a reference // It makes sense to box reference receiver before storing into a temp - no need to box/constrain twice. rewrittenReceiver = BoxReceiver(rewrittenReceiver, memberContainingType); } BoundAssignmentOperator assignmentToTemp; var receiverTemp = this.factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: rewrittenReceiver.Type.IsReferenceType ? RefKind.None : RefKind.Ref); stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes. // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of properties. return new BoundPropertyAccess(prop.Syntax, receiverTemp, prop.PropertySymbol, prop.ResultKind, prop.Type); } case BoundKind.DynamicMemberAccess: { var memberAccess = (BoundDynamicMemberAccess)originalLHS; if (!NeedsTemp(memberAccess.Receiver)) { return memberAccess; } // store receiver to temp: var rewrittenReceiver = VisitExpression(memberAccess.Receiver); BoundAssignmentOperator assignmentToTemp; var receiverTemp = this.factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); return new BoundDynamicMemberAccess(memberAccess.Syntax, receiverTemp, memberAccess.TypeArgumentsOpt, memberAccess.Name, memberAccess.Invoked, memberAccess.Indexed, memberAccess.Type); } case BoundKind.IndexerAccess: { BoundIndexerAccess indexerAccess = (BoundIndexerAccess)originalLHS; var receiverOpt = indexerAccess.ReceiverOpt; Debug.Assert(receiverOpt != null); BoundExpression transformedReceiver; if (NeedsTemp(receiverOpt)) { BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); if (rewrittenReceiver.Type.IsTypeParameter() && rewrittenReceiver.Type.IsReferenceType) { var memberContainingType = indexerAccess.Indexer.ContainingType; // From the verifier prospective type parameters do not contain fields or methods. // the instance must be boxed/constrained to access the member even if known to be a reference // It makes sense to box reference receiver before storing into a temp - no need to box/constrain twice. rewrittenReceiver = BoxReceiver(rewrittenReceiver, memberContainingType); } BoundAssignmentOperator assignmentToTemp; var receiverTemp = this.factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: rewrittenReceiver.Type.IsReferenceType ? RefKind.None : RefKind.Ref); transformedReceiver = receiverTemp; stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); } else { transformedReceiver = VisitExpression(receiverOpt); } // Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // we have to preserve both the source-code order of the side effects and the side effects // only being executed once. // // This is a subtly different problem than the problem faced by the conventional call // rewriter; with the conventional call rewriter we already know that the side effects // will only be executed once because the arguments are only being pushed on the stack once. // In a compound equality operator on an indexer the indices are placed on the stack twice. // That is to say, if you have: // // C().M(z : Z(), x : X(), y : Y()) // // then we can rewrite that into // // tempc = C() // tempz = Z() // tempc.M(X(), Y(), tempz) // // See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // temporaries in // // C().Collection[z : Z(), x : X(), y : Y()] += 1; // // because we have to ensure not just that Z() happens first, but in addition that X() and Y() are only // called once. We have to generate this as // // tempc = C().Collection // tempz = Z() // tempx = X() // tempy = Y() // tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // // Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // However, we can still do the optimization where constants are not stored in // temporaries; if we have // // C().Collection[z : 123, y : Y(), x : X()] += 1; // // Then we can generate that as // // tempc = C().Collection // tempx = X() // tempy = Y() // tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; ImmutableArray<BoundExpression> rewrittenArguments = VisitList(indexerAccess.Arguments); CSharpSyntaxNode syntax = indexerAccess.Syntax; PropertySymbol indexer = indexerAccess.Indexer; ImmutableArray<RefKind> argumentRefKinds = indexerAccess.ArgumentRefKindsOpt; bool expanded = indexerAccess.Expanded; ImmutableArray<int> argsToParamsOpt = indexerAccess.ArgsToParamsOpt; ImmutableArray<ParameterSymbol> parameters = indexer.Parameters; BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder<BoundAssignmentOperator> storesToTemps = ArrayBuilder<BoundAssignmentOperator>.GetInstance(rewrittenArguments.Length); ArrayBuilder<RefKind> refKinds = ArrayBuilder<RefKind>.GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { BoundExpression array = BuildParamsArray(syntax, indexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); BoundAssignmentOperator storeToTemp; var boundTemp = this.factory.StoreToTemp(array, out storeToTemp); stores.Add(storeToTemp); temps.Add(boundTemp.LocalSymbol); actualArguments[actualArguments.Length - 1] = boundTemp; } // Step three: Now fill in the optional arguments. (Dev11 uses the // getter for optional arguments in compound assignments.) var getMethod = indexer.GetOwnOrInheritedGetMethod(); Debug.Assert((object)getMethod != null); InsertMissingOptionalArguments(syntax, getMethod.Parameters, actualArguments); // For a call, step four would be to optimize away some of the temps. However, we need them all to prevent // duplicate side-effects, so we'll skip that step. if (indexer.ContainingType.IsComImport) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps); } rewrittenArguments = actualArguments.AsImmutableOrNull(); foreach (BoundAssignmentOperator tempAssignment in storesToTemps) { temps.Add(((BoundLocal)tempAssignment.Left).LocalSymbol); stores.Add(tempAssignment); } storesToTemps.Free(); argumentRefKinds = GetRefKindsOrNull(refKinds); refKinds.Free(); // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes. // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of indexers. return new BoundIndexerAccess( syntax, transformedReceiver, indexer, rewrittenArguments, default(ImmutableArray<string>), argumentRefKinds, false, default(ImmutableArray<int>), indexerAccess.Type); } case BoundKind.Local: case BoundKind.Parameter: case BoundKind.ThisReference: // a special kind of parameter // No temporaries are needed. Just generate local = local + value return originalLHS; case BoundKind.DeclarationExpression: var rewrittenDeclExpr = (BoundExpression)VisitDeclarationExpression((BoundDeclarationExpression)originalLHS); switch (rewrittenDeclExpr.Kind) { case BoundKind.Local: return rewrittenDeclExpr; case BoundKind.Sequence: var sequence = (BoundSequence)rewrittenDeclExpr; stores.AddRange(sequence.SideEffects); temps.AddRange(sequence.Locals); if (sequence.Value.Kind == BoundKind.Local) { return sequence.Value; } break; } throw ExceptionUtilities.Unreachable; case 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)originalLHS; BoundExpression receiverOpt = fieldAccess.ReceiverOpt; //If the receiver is static or is the receiver is of kind "Base" or "this", then we can just generate field = field + value if (receiverOpt == null || fieldAccess.FieldSymbol.IsStatic || !NeedsTemp(receiverOpt)) { return fieldAccess; } if (receiverOpt.Type.IsReferenceType) { Debug.Assert(receiverOpt.Kind != BoundKind.TypeExpression); BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); if (rewrittenReceiver.Type.IsTypeParameter()) { var memberContainingType = fieldAccess.FieldSymbol.ContainingType; // From the verifier prospective type parameters do not contain fields or methods. // the instance must be "boxed" to access the field // It makes sense to box receiver before storing into a temp - no need to box twice. rewrittenReceiver = BoxReceiver(rewrittenReceiver, memberContainingType); } BoundAssignmentOperator assignmentToTemp; var receiverTemp = this.factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(receiverTemp.LocalSymbol); return new BoundFieldAccess(fieldAccess.Syntax, receiverTemp, fieldAccess.FieldSymbol, null); } break; } case BoundKind.DynamicIndexerAccess: { var indexerAccess = (BoundDynamicIndexerAccess)originalLHS; BoundExpression loweredReceiver; if (NeedsTemp(indexerAccess.ReceiverOpt)) { BoundAssignmentOperator assignmentToTemp; var temp = this.factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredReceiver = temp; } else { loweredReceiver = indexerAccess.ReceiverOpt; } var arguments = indexerAccess.Arguments; var loweredArguments = new BoundExpression[arguments.Length]; for (int i = 0; i < arguments.Length; i++) { if (NeedsTemp(arguments[i])) { BoundAssignmentOperator assignmentToTemp; var temp = this.factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, refKind: indexerAccess.ArgumentRefKindsOpt.RefKinds(i)); stores.Add(assignmentToTemp); temps.Add(temp.LocalSymbol); loweredArguments[i] = temp; } else { loweredArguments[i] = arguments[i]; } } return new BoundDynamicIndexerAccess( indexerAccess.Syntax, loweredReceiver, loweredArguments.AsImmutableOrNull(), indexerAccess.ArgumentNamesOpt, indexerAccess.ArgumentRefKindsOpt, indexerAccess.ApplicableIndexers, indexerAccess.Type); } case BoundKind.ArrayAccess: if (isDynamicAssignment) { // In non-dynamic array[index] op= R we emit: // T& tmp = &array[index]; // *tmp = *L op R; // where T is the type of L. // // If L is an array access, the assignment is dynamic, the compile-time of the array is dynamic[] // and the runtime type of the array is not object[] (but e.g. string[]) the pointer approach is broken. // T is Object in such case and we can't take a read-write pointer of type Object& to an array element of non-object type. // // In this case we rewrite the assignment as follows: // // E t_array = array; // I t_index = index; (possibly more indices) // T value = t_array[t_index]; // t_array[t_index] = value op R; var arrayAccess = (BoundArrayAccess)originalLHS; var loweredArray = VisitExpression(arrayAccess.Expression); var loweredIndices = VisitList(arrayAccess.Indices); return SpillArrayElementAccess(loweredArray, loweredIndices, stores, temps); } break; case BoundKind.PointerElementAccess: case BoundKind.PointerIndirectionOperator: case BoundKind.RefValueOperator: break; default: throw ExceptionUtilities.UnexpectedValue(originalLHS.Kind); } // 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 // Rewrite the variable. Here we depend on the fact that the only forms // rewritten here are rewritten the same for lvalues and rvalues. BoundExpression rewrittenVariable = VisitExpression(originalLHS); BoundAssignmentOperator assignmentToTemp2; var variableTemp = this.factory.StoreToTemp(rewrittenVariable, out assignmentToTemp2, refKind: RefKind.Ref); stores.Add(assignmentToTemp2); temps.Add(variableTemp.LocalSymbol); return variableTemp; }
/// <summary> /// Process tempStores and add them as side-effects to arguments where needed. The return /// value tells how many temps are actually needed. For unnecessary temps the corresponding /// temp store will be cleared. /// </summary> private static int MergeArgumentsAndSideEffects( BoundExpression[] arguments, ArrayBuilder<RefKind> refKinds, ArrayBuilder<BoundAssignmentOperator> tempStores) { Debug.Assert(arguments != null); Debug.Assert(refKinds != null); Debug.Assert(tempStores != null); int tempsRemainedInUse = tempStores.Count; // Suppose we've got temporaries: t0 = A(), t1 = B(), t2 = C(), t4 = D(), t5 = E() // and arguments: t0, t2, t1, t4, 10, t5 // // We wish to produce arguments list: A(), SEQ(t1=B(), C()), t1, D(), 10, E() // // Our algorithm essentially finds temp stores that must happen before given argument // load, and if there are any they become side effects of the given load. // Stores immediately followed by loads of the same thing can be eliminated. // // Constraints: // Stores must happen before corresponding loads. // Stores cannot move relative to other stores. If arg was movable it would not need a temp. int firstUnclaimedStore = 0; for (int a = 0; a < arguments.Length; ++a) { var argument = arguments[a]; // if argument is a load, search for corresponding store. if store is found, extract // the actual expression we were storing and add it as an argument - this one does // not need a temp. if there are any unclaimed stores before the found one, add them // as side effects that precede this arg, they cannot happen later. // NOTE: missing optional parameters are not filled yet and therefore nulls - no need to do anything for them if (argument?.Kind == BoundKind.Local) { var correspondingStore = -1; for (int i = firstUnclaimedStore; i < tempStores.Count; i++) { if (tempStores[i].Left == argument) { correspondingStore = i; break; } } // store found? if (correspondingStore != -1) { var value = tempStores[correspondingStore].Right; // When we created the temp, we dropped the argument RefKind // since the local contained its own RefKind. Since we're removing // the temp, the argument RefKind needs to be restored. refKinds[a] = ((BoundLocal)argument).LocalSymbol.RefKind; // the matched store will not need to go into side-effects, only ones before it will // remove the store to signal that we are not using its temp. tempStores[correspondingStore] = null; tempsRemainedInUse--; // no need for side-effects? // just combine store and load if (correspondingStore == firstUnclaimedStore) { arguments[a] = value; } else { var sideeffects = new BoundExpression[correspondingStore - firstUnclaimedStore]; for (int s = 0; s < sideeffects.Length; s++) { sideeffects[s] = tempStores[firstUnclaimedStore + s]; } arguments[a] = new BoundSequence( value.Syntax, // this sequence does not own locals. Note that temps that // we use for the rewrite are stored in one arg and loaded // in another so they must live in a scope above. ImmutableArray<LocalSymbol>.Empty, sideeffects.AsImmutableOrNull(), value, value.Type); } firstUnclaimedStore = correspondingStore + 1; } } } Debug.Assert(firstUnclaimedStore == tempStores.Count, "not all side-effects were claimed"); return tempsRemainedInUse; }
private BoundExpression BindAnonymousObjectCreation(AnonymousObjectCreationExpressionSyntax node, DiagnosticBag diagnostics) { // prepare var initializers = node.Initializers; int fieldCount = initializers.Count; bool hasError = false; // bind field initializers BoundExpression[] boundExpressions = new BoundExpression[fieldCount]; AnonymousTypeField[] fields = new AnonymousTypeField[fieldCount]; CSharpSyntaxNode[] fieldSyntaxNodes = new CSharpSyntaxNode[fieldCount]; // WARNING: Note that SemanticModel.GetDeclaredSymbol for field initializer node relies on // the fact that the order of properties in anonymous type template corresponds // 1-to-1 to the appropriate filed initializer syntax nodes; This means such // correspondence must be preserved all the time including erroneos scenarios // set of names already used HashSet <string> uniqueFieldNames = new HashSet <string>(); for (int i = 0; i < fieldCount; i++) { AnonymousObjectMemberDeclaratorSyntax fieldInitializer = initializers[i]; NameEqualsSyntax nameEquals = fieldInitializer.NameEquals; ExpressionSyntax expression = fieldInitializer.Expression; SyntaxToken nameToken = default(SyntaxToken); if (nameEquals != null) { nameToken = nameEquals.Name.Identifier; } else { nameToken = expression.ExtractAnonymousTypeMemberName(); } hasError = hasError || expression.HasErrors; boundExpressions[i] = this.BindValue(expression, diagnostics, BindValueKind.RValue); // check the name to be unique string fieldName = null; if (nameToken.CSharpKind() == SyntaxKind.IdentifierToken) { fieldName = nameToken.ValueText; if (uniqueFieldNames.Contains(fieldName)) { // name duplication Error(diagnostics, ErrorCode.ERR_AnonymousTypeDuplicatePropertyName, fieldInitializer); hasError = true; fieldName = null; } else { uniqueFieldNames.Add(fieldName); } } else { // there is something wrong with field's name hasError = true; } // calculate the expression's type and report errors if needed TypeSymbol fieldType = GetAnonymousTypeFieldType(boundExpressions[i], fieldInitializer, diagnostics, ref hasError); // build anonymous type field descriptor fieldSyntaxNodes[i] = (nameToken.CSharpKind() == SyntaxKind.IdentifierToken) ? (CSharpSyntaxNode)nameToken.Parent : fieldInitializer; fields[i] = new AnonymousTypeField(fieldName == null ? '$' + i.ToString() : fieldName, fieldSyntaxNodes[i].Location, fieldType); // NOTE: ERR_InvalidAnonymousTypeMemberDeclarator (CS0746) would be generated by parser if needed } // Create anonymous type AnonymousTypeManager manager = this.Compilation.AnonymousTypeManager; AnonymousTypeDescriptor descriptor = new AnonymousTypeDescriptor(fields.AsImmutableOrNull(), node.NewKeyword.GetLocation()); NamedTypeSymbol anonymousType = manager.ConstructAnonymousTypeSymbol(descriptor); // declarators - bound nodes created for providing semantic info // on anonymous type fields having explicitly specified name ArrayBuilder <BoundAnonymousPropertyDeclaration> declarators = ArrayBuilder <BoundAnonymousPropertyDeclaration> .GetInstance(); for (int i = 0; i < fieldCount; i++) { NameEqualsSyntax explicitName = initializers[i].NameEquals; if (explicitName != null) { AnonymousTypeField field = fields[i]; if (field.Name != null) { // get property symbol and create a bound property declaration node foreach (var symbol in anonymousType.GetMembers(field.Name)) { if (symbol.Kind == SymbolKind.Property) { declarators.Add(new BoundAnonymousPropertyDeclaration(fieldSyntaxNodes[i], (PropertySymbol)symbol, field.Type)); break; } } } } } // check if anonymous object creation is allowed in this context if (!this.IsAnonymousTypesAllowed()) { Error(diagnostics, ErrorCode.ERR_AnonymousTypeNotAvailable, node.NewKeyword); hasError = true; } // Finally create a bound node return(new BoundAnonymousObjectCreationExpression( node, anonymousType.InstanceConstructors[0], boundExpressions.AsImmutableOrNull(), declarators.ToImmutableAndFree(), anonymousType, hasError)); }
/// <summary> /// Rewrites arguments of an invocation according to the receiving method or indexer. /// It is assumed that the each argument has already been lowered, but we may need /// additional rewriting for the arguments, such as generating a params array, re-ordering /// arguments based on argsToParamsOpt map, inserting arguments for optional parameters, etc. /// 'optionalParametersMethod' is the method used for values of any optional parameters. /// For indexers, this method must be an accessor, and for methods it must be the method /// itself. 'optionalParametersMethod' is needed for indexers since the getter and setter /// may have distinct optional parameter values. /// </summary> private ImmutableArray <BoundExpression> MakeArguments( CSharpSyntaxNode syntax, ImmutableArray <BoundExpression> rewrittenArguments, Symbol methodOrIndexer, MethodSymbol optionalParametersMethod, bool expanded, ImmutableArray <int> argsToParamsOpt, ref ImmutableArray <RefKind> argumentRefKindsOpt, out ImmutableArray <LocalSymbol> temps, bool invokedAsExtensionMethod = false) { // Either the methodOrIndexer is a property, in which case the method used // for optional parameters is an accessor of that property (or an overridden // property), or the methodOrIndexer is used for optional parameters directly. Debug.Assert(((methodOrIndexer.Kind == SymbolKind.Property) && optionalParametersMethod.IsAccessor()) || ReferenceEquals(methodOrIndexer, optionalParametersMethod)); // We need to do a fancy rewrite under the following circumstances: // (1) a params array is being used; we need to generate the array. // (2) there were named arguments that reordered the arguments; we might // have to generate temporaries to ensure that the arguments are // evaluated in source code order, not the actual call order. // (3) there were optional parameters that had no corresponding arguments. // // If none of those are the case then we can just take an early out. // An applicable "vararg" method could not possibly be applicable in its expanded // form, and cannot possibly have named arguments or used optional parameters, // because the __arglist() argument has to be positional and in the last position. if (methodOrIndexer.GetIsVararg()) { Debug.Assert(rewrittenArguments.Length == methodOrIndexer.GetParameterCount() + 1); Debug.Assert(argsToParamsOpt.IsDefault); Debug.Assert(!expanded); temps = default(ImmutableArray <LocalSymbol>); return(rewrittenArguments); } var receiverNamedType = invokedAsExtensionMethod ? ((MethodSymbol)methodOrIndexer).Parameters[0].Type as NamedTypeSymbol : methodOrIndexer.ContainingType; bool isComReceiver = (object)receiverNamedType != null && receiverNamedType.IsComImport; if (rewrittenArguments.Length == methodOrIndexer.GetParameterCount() && argsToParamsOpt.IsDefault && !expanded && !isComReceiver) { temps = default(ImmutableArray <LocalSymbol>); return(rewrittenArguments); } // We have: // * a list of arguments, already converted to their proper types, // in source code order. Some optional arguments might be missing. // * a map showing which parameter each argument corresponds to. If // this is null, then the argument to parameter mapping is one-to-one. // * the ref kind of each argument, in source code order. That is, whether // the argument was marked as ref, out, or value (neither). // * a method symbol. // * whether the call is expanded or normal form. // We rewrite the call so that: // * if in its expanded form, we create the params array. // * if the call requires reordering of arguments because of named arguments, temporaries are generated as needed // Doing this transformation can move around refness in interesting ways. For example, consider // // A().M(y : ref B()[C()], x : out D()); // // This will be created as a call with receiver A(), symbol M, argument list ( B()[C()], D() ), // name list ( y, x ) and ref list ( ref, out ). We can rewrite this into temporaries: // // A().M( // seq ( ref int temp_y = ref B()[C()], out D() ), // temp_y ); // // Now we have a call with receiver A(), symbol M, argument list as shown, no name list, // and ref list ( out, value ). We do not want to pass a *ref* to temp_y; the temporary // storage is not the thing being ref'd! We want to pass the *value* of temp_y, which // *contains* a reference. // We attempt to minimize the number of temporaries required. Arguments which neither // produce nor observe a side effect can be placed into their proper position without // recourse to a temporary. For example: // // Where(predicate: x=>x.Length!=0, sequence: S()) // // can be rewritten without any temporaries because the conversion from lambda to // delegate does not produce any side effect that could be observed by S(). // // By contrast: // // Foo(z: this.p, y: this.Q(), x: (object)10) // // The boxing of 10 can be reordered, but the fetch of this.p has to happen before the // call to this.Q() because the call could change the value of this.p. // // We start by binding everything that is not obviously reorderable as a temporary, and // then run an optimizer to remove unnecessary temporaries. ImmutableArray <ParameterSymbol> parameters = methodOrIndexer.GetParameters(); BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter. ArrayBuilder <BoundAssignmentOperator> storesToTemps = ArrayBuilder <BoundAssignmentOperator> .GetInstance(rewrittenArguments.Length); ArrayBuilder <RefKind> refKinds = ArrayBuilder <RefKind> .GetInstance(parameters.Length, RefKind.None); // Step one: Store everything that is non-trivial into a temporary; record the // stores in storesToTemps and make the actual argument a reference to the temp. // Do not yet attempt to deal with params arrays or optional arguments. BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKindsOpt, rewrittenArguments, actualArguments, refKinds, storesToTemps); // Step two: If we have a params array, build the array and fill in the argument. if (expanded) { actualArguments[actualArguments.Length - 1] = BuildParamsArray(syntax, methodOrIndexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]); } // Step three: Now fill in the optional arguments. InsertMissingOptionalArguments(syntax, optionalParametersMethod.Parameters, actualArguments); // Step four: all the arguments are now in place. Optimize away unnecessary temporaries. // Necessary temporaries have their store instructions merged into the appropriate // argument expression. ArrayBuilder <LocalSymbol> temporariesBuilder = ArrayBuilder <LocalSymbol> .GetInstance(); OptimizeTemporaries(actualArguments, refKinds, storesToTemps, temporariesBuilder); if (isComReceiver) { RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temporariesBuilder); } temps = temporariesBuilder.ToImmutableAndFree(); storesToTemps.Free(); // * The refkind map is now filled out to match the arguments. // * The list of parameter names is now null because the arguments have been reordered. // * The args-to-params map is now null because every argument exactly matches its parameter. // * The call is no longer in its expanded form. argumentRefKindsOpt = GetRefKindsOrNull(refKinds); refKinds.Free(); return(actualArguments.AsImmutableOrNull()); }