public override BoundNode VisitFieldAccess(BoundFieldAccess node) { Debug.Assert(node != null); var constantValue = node.ConstantValue; if (!node.HasErrors && constantValue != null && constantValue.IsBad) { //we depend on whoever set the value to Bad to have added an appropriate diagnostic return new BoundFieldAccess(node.Syntax, node.SyntaxTree, node.ReceiverOpt, node.FieldSymbol, constantValue, true); } return base.VisitFieldAccess(node); }
internal BoundExpression ToBoundExpression(CSharpSyntaxNode syntax) { var expr = this.DisplayClassInstance.ToBoundExpression(syntax); var fields = ArrayBuilder<FieldSymbol>.GetInstance(); fields.AddRange(this.DisplayClassFields); fields.ReverseContents(); foreach (var field in fields) { expr = new BoundFieldAccess(syntax, expr, field, constantValueOpt: null) { WasCompilerGenerated = true }; } fields.Free(); return expr; }
internal void Parse(BoundFieldAccess boundFieldAccess) { base.Parse(boundFieldAccess); this.Field = boundFieldAccess.FieldSymbol; if (boundFieldAccess.ReceiverOpt != null) { this.ReceiverOpt = Deserialize(boundFieldAccess.ReceiverOpt) as Expression; } var memberAccessExpressionSyntax = boundFieldAccess.Syntax as MemberAccessExpressionSyntax; if (memberAccessExpressionSyntax != null) { this.PointerAccess = memberAccessExpressionSyntax.Kind() == SyntaxKind.PointerMemberAccessExpression; } }
public override BoundNode VisitFieldAccess(BoundFieldAccess node) { BoundExpression receiverOpt = (BoundExpression)this.Visit(node.ReceiverOpt); TypeSymbol type = this.VisitType(node.Type); var fieldSymbol = ((FieldSymbol)node.FieldSymbol.OriginalDefinition).AsMember( (NamedTypeSymbol)this.VisitType(node.FieldSymbol.ContainingType) ); return(node.Update( receiverOpt, fieldSymbol, node.ConstantValueOpt, node.ResultKind, type )); }
internal BoundExpression ToBoundExpression(SyntaxNode syntax) { var expr = this.DisplayClassInstance.ToBoundExpression(syntax); var fields = ArrayBuilder <FieldSymbol> .GetInstance(); fields.AddRange(this.DisplayClassFields); fields.ReverseContents(); foreach (var field in fields) { expr = new BoundFieldAccess(syntax, expr, field, constantValueOpt: null) { WasCompilerGenerated = true }; } fields.Free(); return(expr); }
private LocalDefinition EmitInstanceFieldAddress( BoundFieldAccess fieldAccess, AddressKind addressKind ) { var field = fieldAccess.FieldSymbol; //NOTE: we are not propagating AddressKind.Constrained here. // the reason is that while Constrained permits calls, it does not permit // taking field addresses, so we have to turn Constrained into writeable. var tempOpt = EmitReceiverRef( fieldAccess.ReceiverOpt, addressKind == AddressKind.Constrained ? AddressKind.Writeable : addressKind ); _builder.EmitOpCode(ILOpCode.Ldflda); EmitSymbolToken(field, fieldAccess.Syntax); // when loading an address of a fixed field, we actually // want to load the address of its "FixedElementField" instead. // Both the buffer backing struct and its only field should be at the same location, // so we could in theory just use address of the struct, but in some contexts that causes // PEVerify errors because the struct has unexpected type. (Ex: struct& when int& is expected) if (field.IsFixedSizeBuffer) { var fixedImpl = field.FixedImplementationType(_module); var fixedElementField = fixedImpl.FixedElementField; // if we get a mildly corrupted FixedImplementationType which does // not happen to have fixedElementField // we just leave address of the whole struct. // // That seems an adequate fallback because: // 1) it should happen only in impossibly rare cases involving malformed types // 2) the address of the struct is same as that of the buffer, just type is wrong. // and that only matters to the verifier and we are in unsafe context anyways. if ((object)fixedElementField != null) { _builder.EmitOpCode(ILOpCode.Ldflda); EmitSymbolToken(fixedElementField, fieldAccess.Syntax); } } return(tempOpt); }
/// <summary> /// May introduce a temp which it will return. (otherwise returns null) /// </summary> private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess, AddressKind addressKind) { FieldSymbol field = fieldAccess.FieldSymbol; if (!HasHome(fieldAccess, addressKind)) { // accessing a field that is not writable (const or readonly) return(EmitAddressOfTempClone(fieldAccess)); } else if (fieldAccess.FieldSymbol.IsStatic) { EmitStaticFieldAddress(field, fieldAccess.Syntax); return(null); } else { return(EmitInstanceFieldAddress(fieldAccess, addressKind)); } }
/// <summary> /// May introduce a temp which it will return. (otherwise returns null) /// </summary> private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess, AddressKind addressKind) { FieldSymbol field = fieldAccess.FieldSymbol; if (!HasHome(fieldAccess, addressKind != AddressKind.ReadOnly)) { // accessing a field that is not writable (const or readonly) return(EmitAddressOfTempClone(fieldAccess)); } else if (fieldAccess.FieldSymbol.IsStatic) { EmitStaticFieldAddress(field, fieldAccess.Syntax); return(null); } else { //NOTE: we are not propagating AddressKind here. // the reason is that while Constrained permits calls, it does not permit // taking field addresses, so we have to turn Constrained into writeable. // It is less error prone to just pass a bool "isReadonly" return(EmitInstanceFieldAddress(fieldAccess, isReadonly: addressKind == AddressKind.ReadOnly)); } }
/// <summary> /// Special HasHome for fields. Fields have homes when they are writable. /// </summary> private bool HasHome(BoundFieldAccess fieldAccess) { // Some field accesses must be values; values do not have homes. if (fieldAccess.IsByValue) { return(false); } FieldSymbol field = fieldAccess.FieldSymbol; // const fields are literal values with no homes if (field.IsConst) { return(false); } if (!field.IsReadOnly) { return(true); } // while readonly fields have home it is not valid to refer to it when not constructing. if (field.ContainingType != _method.ContainingType) { return(false); } if (field.IsStatic) { return(_method.MethodKind == MethodKind.StaticConstructor); } else { return(_method.MethodKind == MethodKind.Constructor && fieldAccess.ReceiverOpt.Kind == BoundKind.ThisReference); } }
public override BoundNode VisitFieldAccess(BoundFieldAccess node) { BoundExpression receiverOpt = (BoundExpression)this.Visit(node.ReceiverOpt); TypeSymbol type = this.VisitType(node.Type); var fieldSymbol = ((FieldSymbol)node.FieldSymbol.OriginalDefinition) .AsMember((NamedTypeSymbol)this.VisitType(node.FieldSymbol.ContainingType)); return node.Update(receiverOpt, fieldSymbol, node.ConstantValueOpt, node.ResultKind, type); }
/// <summary> /// May introduce a temp which it will return. (otherwise returns null) /// </summary> private LocalDefinition EmitInstanceFieldAddress(BoundFieldAccess fieldAccess) { var field = fieldAccess.FieldSymbol; var tempOpt = EmitReceiverRef(fieldAccess.ReceiverOpt); builder.EmitOpCode(ILOpCode.Ldflda); EmitSymbolToken(field, fieldAccess.Syntax); // when loading an address of a fixed field, we actually // want to load the address of its "FixedElementField" instead. // Both the buffer backing struct and its only field should be at the same location, // so we could in theory just use address of the struct, but in some contexts that causes // PEVerify errors because the struct has unexpected type. (Ex: struct& when int& is expected) if (field.IsFixed) { var fixedImpl = field.FixedImplementationType(this.module); var fixedElementField = fixedImpl.FixedElementField; // if we get a mildly corrupted FixedImplementationType which does // not happen to have fixedElementField // we just leave address of the whole struct. // // That seems an adequate fallback because: // 1) it should happen only in impossibly rare cases involving malformed types // 2) the address of the struct is same as that of the buffer, just type is wrong. // and that only matters to the verifier and we are in unsafe context anyways. if ((object)fixedElementField != null) { builder.EmitOpCode(ILOpCode.Ldflda); EmitSymbolToken(fixedElementField, fieldAccess.Syntax); } } return tempOpt; }
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); }
/// <summary> /// May introduce a temp which it will return. (otherwise returns null) /// </summary> private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess) { FieldSymbol field = fieldAccess.FieldSymbol; if (!HasHome(fieldAccess)) { // accessing a field that is not writeable (const or readonly) return EmitAddressOfTempClone(fieldAccess); } else if (fieldAccess.FieldSymbol.IsStatic) { EmitStaticFieldAddress(field, fieldAccess.Syntax); return null; } else { return EmitInstanceFieldAddress(fieldAccess); } }
/// <summary> /// Special HasHome for fields. Fields have homes when they are writeable. /// </summary> private bool HasHome(BoundFieldAccess fieldAccess) { FieldSymbol field = fieldAccess.FieldSymbol; // const fields are literal values with no homes if (field.IsConst) { return false; } if (!field.IsReadOnly) { return true; } // while readonly fields have home it is not valid to refer to it when not constructing. if (field.ContainingType != method.ContainingType) { return false; } if (field.IsStatic) { return method.MethodKind == MethodKind.StaticConstructor; } else { return method.MethodKind == MethodKind.Constructor && fieldAccess.ReceiverOpt.Kind == BoundKind.ThisReference; } }
public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) { if (node.HasErrors) { return node; } // There are five possible cases. // // Case 1: receiver.Prop += value is transformed into // temp = receiver // temp.Prop = temp.Prop + value // and a later rewriting will turn that into calls to getters and setters. // // Case 2: collection[i1, i2, i3] += value is transformed into // tc = collection // t1 = i1 // t2 = i2 // t3 = i3 // tc[t1, t2, t3] = tc[t1, t2, t3] + value // and again, a later rewriting will turn that into getters and setters of the indexer. // // Case 3: local += value (and param += value) needs no temporaries; it simply // becomes local = local + value. // // Case 4: staticField += value needs no temporaries either. However, classInst.field += value becomes // temp = classInst // temp.field = temp.field + value // // Case 5: otherwise, it must be structVariable.field += value or array[index] += value. Either way // we have a variable on the left. Transform it into: // ref temp = ref variable // temp = temp + value var temps = ArrayBuilder<LocalSymbol>.GetInstance(); var stores = ArrayBuilder<BoundExpression>.GetInstance(); // This will be filled in with the LHS that uses temporaries to prevent // double-evaluation of side effects. BoundExpression transformedLHS = null; if (node.Left.Kind == BoundKind.PropertyAccess) { // We need to stash away the receiver so that it does not get evaluated twice. // If the receiver is classified as a value of reference type then we can simply say // // R temp = receiver // temp.prop = temp.prop + rhs // // But if the receiver is classified as a variable of struct type then we // cannot make a copy of the value; we need to make sure that we mutate // the original receiver, not the copy. We have to generate // // ref R temp = ref receiver // temp.prop = temp.prop + rhs // // The rules of C# (in section 7.17.1) require that if you have receiver.prop // as the target of an assignment such that receiver is a value type, it must // be classified as a variable. If we've gotten this far in the rewriting, // assume that was the case. var prop = (BoundPropertyAccess)node.Left; // If the property is static then we can just generate prop = prop + value if (prop.ReceiverOpt == null) { transformedLHS = prop; } else { // Can we ever avoid storing the receiver in a temp? If the receiver is a variable then it // might be modified by the computation of the getter, the value, or the operation. // The receiver cannot be a null constant or constant of value type. It could be a // constant of string type, but there are no mutable properties of a string. // Similarly, there are no mutable properties of a Type object, so the receiver // cannot be a typeof(T) expression. The only situation I can think of where we could // optimize away the temp is if the receiver is a readonly field of reference type, // we are not in a constructor, and the receiver of the *field*, if any, is also idempotent. // It doesn't seem worthwhile to pursue an optimization for this exceedingly rare case. var rewrittenReceiver = (BoundExpression)Visit(prop.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundPropertyAccess(prop.Syntax, prop.SyntaxTree, receiverTemp.Item2, prop.PropertySymbol, prop.Type); } } else if (node.Left.Kind == BoundKind.IndexerAccess) { var indexer = (BoundIndexerAccess)node.Left; BoundExpression transformedReceiver = null; if (indexer.ReceiverOpt != null) { var rewrittenReceiver = (BoundExpression)Visit(indexer.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); transformedReceiver = receiverTemp.Item2; stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); } // UNDONE: Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // UNDONE: we have to preserve both the source-code order of the side effects and the side effects // UNDONE: only being executed once. // UNDONE: // UNDONE: This is a subtly different problem than the problem faced by the conventional call // UNDONE: rewriter; with the conventional call rewriter we already know that the side effects // UNDONE: will only be executed once because the arguments are only being pushed on the stack once. // UNDONE: In a compound equality operator on an indexer the indices are placed on the stack twice. // UNDONE: That is to say, if you have: // UNDONE: // UNDONE: C().M(z : Z(), x : X(), y : Y()) // UNDONE: // UNDONE: then we can rewrite that into // UNDONE: // UNDONE: tempc = C() // UNDONE: tempz = Z() // UNDONE: tempc.M(X(), Y(), tempz) // UNDONE: // UNDONE: See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // UNDONE: temporaries in // UNDONE: // UNDONE: C().Collection[z : Z(), x : X(), y : Y()] += 1; // UNDONE: // UNDONE: because we have to ensure not just that Z() happens first, but in additioan that X() and Y() are only // UNDONE: called once. We have to generate this as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempz = Z() // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // UNDONE: // UNDONE: Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // UNDONE: However, we can still do the optimization where constants are not stored in // UNDONE: temporaries; if we have // UNDONE: // UNDONE: C().Collection[z : 123, y : Y(), x : X()] += 1; // UNDONE: // UNDONE: Then we can generate that as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; // UNDONE: // UNDONE: For now, we'll punt on both problems, as indexers are not implemented yet anyway. // UNDONE: We'll just generate one temporary for each argument. This will work, but in the // UNDONE: subsequent rewritings will generate more unnecessary temporaries. var transformedArguments = ArrayBuilder<BoundExpression>.GetInstance(); foreach (var argument in indexer.Arguments) { var rewrittenArgument = (BoundExpression)Visit(argument); var argumentTemp = TempHelpers.StoreToTemp(rewrittenArgument, RefKind.None, containingSymbol); transformedArguments.Add(argumentTemp.Item2); stores.Add(argumentTemp.Item1); temps.Add(argumentTemp.Item2.LocalSymbol); } transformedLHS = new BoundIndexerAccess(indexer.Syntax, indexer.SyntaxTree, transformedArguments.ToReadOnlyAndFree(), transformedReceiver, indexer.IndexerSymbol, indexer.Type); } else if (node.Left.Kind == BoundKind.Local || node.Left.Kind == BoundKind.Parameter) { // No temporaries are needed. Just generate local = local + value transformedLHS = node.Left; } else if (node.Left.Kind == BoundKind.FieldAccess) { // * If the field is static then no temporaries are needed. // * If the field is not static and the receiver is of reference type then generate t = r; t.f = t.f + value // * If the field is not static and the receiver is a variable of value type then we'll fall into the // general variable case below. var fieldAccess = (BoundFieldAccess)node.Left; if (fieldAccess.ReceiverOpt == null) { transformedLHS = fieldAccess; } else if (!fieldAccess.ReceiverOpt.Type.IsValueType) { var rewrittenReceiver = (BoundExpression)Visit(fieldAccess.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundFieldAccess(fieldAccess.Syntax, fieldAccess.SyntaxTree, receiverTemp.Item2, fieldAccess.FieldSymbol, null); } } if (transformedLHS == null) { // We made no transformation above. Either we have array[index] += value or // structVariable.field += value; either way we have a potentially complicated variable- // producing expression on the left. Generate // ref temp = ref variable; temp = temp + value var rewrittenVariable = (BoundExpression)Visit(node.Left); var variableTemp = TempHelpers.StoreToTemp(rewrittenVariable, RefKind.Ref, containingSymbol); stores.Add(variableTemp.Item1); temps.Add(variableTemp.Item2.LocalSymbol); transformedLHS = variableTemp.Item2; } // OK, we now have the temporary declarations, the temporary stores, and the transformed left hand side. // We need to generate // // xlhs = (FINAL)((LEFT)xlhs op rhs) // // And then wrap it up with the generated temporaries. // // (The right hand side has already been converted to the type expected by the operator.) BoundExpression opLHS = BoundConversion.SynthesizedConversion(transformedLHS, node.LeftConversion, node.Operator.LeftType); Debug.Assert(node.Right.Type == node.Operator.RightType); BoundExpression op = new BoundBinaryOperator(null, null, node.Operator.Kind, opLHS, node.Right, null, node.Operator.ReturnType); BoundExpression opFinal = BoundConversion.SynthesizedConversion(op, node.FinalConversion, node.Left.Type); BoundExpression assignment = new BoundAssignmentOperator(null, null, transformedLHS, opFinal, node.Left.Type); // OK, at this point we have: // // * temps evaluating and storing portions of the LHS that must be evaluated only once. // * the "transformed" left hand side, rebuilt to use temps where necessary // * the assignment "xlhs = (FINAL)((LEFT)xlhs op (RIGHT)rhs)" // // Notice that we have recursively rewritten the bound nodes that are things stored in // the temps, but we might have more rewriting to do on the assignment. There are three // conversions in there that might be lowered to method calls, an operator that might // be lowered to delegate combine, string concat, and so on, and don't forget, we // haven't lowered the right hand side at all! Let's rewrite all these things at once. BoundExpression rewrittenAssignment = (BoundExpression)Visit(assignment); BoundExpression result = (temps.Count == 0) ? rewrittenAssignment : new BoundSequence(null, null, temps.ToReadOnly(), stores.ToReadOnly(), rewrittenAssignment, rewrittenAssignment.Type); temps.Free(); stores.Free(); return result; }
private void EmitFieldLoad(BoundFieldAccess fieldAccess, bool used) { var field = fieldAccess.FieldSymbol; if (!used) { // fetching unused captured frame is a no-op (like reading "this") if (field.IsCapturedFrame) { return; } // Accessing a volatile field is sideeffecting because it establishes an acquire fence. // Otherwise, accessing an unused instance field on a struct is a noop. Just emit an unused receiver. if (!field.IsVolatile && !field.IsStatic && fieldAccess.ReceiverOpt.Type.IsVerifierValue()) { EmitExpression(fieldAccess.ReceiverOpt, used: false); return; } } Debug.Assert(!field.IsConst || field.ContainingType.SpecialType == SpecialType.System_Decimal, "rewriter should lower constant fields into constant expressions"); // static field access is sideeffecting since it gurantees that ..ctor has run. // we emit static accesses even if unused. if (field.IsStatic) { if (field.IsVolatile) { _builder.EmitOpCode(ILOpCode.Volatile); } _builder.EmitOpCode(ILOpCode.Ldsfld); EmitSymbolToken(field, fieldAccess.Syntax); } else { var receiver = fieldAccess.ReceiverOpt; var fieldType = field.Type; if (fieldType.IsValueType && (object)fieldType == (object)receiver.Type) { //Handle emitting a field of a self-containing struct (only possible in mscorlib) //since "val.field" is the same as val, we only need to emit val. EmitExpression(receiver, used); } else { var temp = EmitFieldLoadReceiver(receiver); if (temp != null) { Debug.Assert(FieldLoadMustUseRef(receiver), "only clr-ambiguous structs use temps here"); FreeTemp(temp); } if (field.IsVolatile) { _builder.EmitOpCode(ILOpCode.Volatile); } _builder.EmitOpCode(ILOpCode.Ldfld); EmitSymbolToken(field, fieldAccess.Syntax); } } EmitPopIfUnused(used); }
private void EmitFieldStore(BoundFieldAccess fieldAccess) { var field = fieldAccess.FieldSymbol; if (field.IsVolatile) { _builder.EmitOpCode(ILOpCode.Volatile); } _builder.EmitOpCode(field.IsStatic ? ILOpCode.Stsfld : ILOpCode.Stfld); EmitSymbolToken(field, fieldAccess.Syntax); }
public override BoundNode VisitFieldAccess(BoundFieldAccess node) { var field = node.FieldSymbol; var receiver = node.ReceiverOpt; // if there are any doubts that receiver is a ref type, // assume we will need an address. (that will prevent scheduling of receiver). if (!field.IsStatic) { if (receiver.Type.IsTypeParameter()) { // type parameters must be boxed to access fields. receiver = VisitExpression(receiver, ExprContext.Box); } else { // need address when assigning to a field and receiver is not a reference // when accessing a field of a struct unless we only need Value and Value is preferred. if (receiver.Type.IsValueType && ( _context == ExprContext.AssignmentTarget || _context == ExprContext.Address || CodeGenerator.FieldLoadMustUseRef(receiver))) { receiver = VisitExpression(receiver, ExprContext.Address); } else { receiver = VisitExpression(receiver, ExprContext.Value); } } } else { // for some reason it could be not null even if field is static... // it seems wrong _counter += 1; receiver = null; } return node.Update(receiver, field, node.ConstantValueOpt, node.ResultKind, node.Type); }
/// <summary> /// Special HasHome for fields. /// Fields have readable homes when they are not constants. /// Fields have writeable homes unless they are readonly and used outside of the constructor. /// </summary> private bool HasHome(BoundFieldAccess fieldAccess, AddressKind addressKind) { FieldSymbol field = fieldAccess.FieldSymbol; // const fields are literal values with no homes. (ex: decimal.Zero) if (field.IsConst) { return(false); } // in readonly situations where ref to a copy is not allowed, consider fields as addressable if (addressKind == AddressKind.ReadOnlyStrict) { return(true); } // ReadOnly references can always be taken unless we are in peverify compat mode if (addressKind == AddressKind.ReadOnly && !EnablePEVerifyCompat()) { return(true); } // Some field accesses must be values; values do not have homes. if (fieldAccess.IsByValue) { return(false); } if (!field.IsReadOnly) { // in a case if we have a writeable struct field with a receiver that only has a readable home we would need to pass it via a temp. // it would be advantageous to make a temp for the field, not for the the outer struct, since the field is smaller and we can get to is by fetching references. // NOTE: this would not be profitable if we have to satisfy verifier, since for verifiability // we would not be able to dig for the inner field using references and the outer struct will have to be copied to a temp anyways. if (!EnablePEVerifyCompat()) { Debug.Assert(!IsReadOnly(addressKind)); var receiver = fieldAccess.ReceiverOpt; if (receiver?.Type.IsValueType == true) { // Check receiver: // has writeable home -> return true - the whole chain has writeable home (also a more common case) // has readable home -> return false - we need to copy the field // otherwise -> return true - the copy will be made at higher level so the leaf field can have writeable home return(HasHome(receiver, addressKind) || !HasHome(receiver, AddressKind.ReadOnly)); } } return(true); } // while readonly fields have home it is not valid to refer to it when not constructing. if (field.ContainingType != _method.ContainingType) { return(false); } if (field.IsStatic) { return(_method.MethodKind == MethodKind.StaticConstructor); } else { return(_method.MethodKind == MethodKind.Constructor && fieldAccess.ReceiverOpt.Kind == BoundKind.ThisReference); } }
private void EmitFieldLoad(BoundFieldAccess fieldAccess, bool used) { var field = fieldAccess.FieldSymbol; //TODO: For static field access this may require ..ctor to run. Is this a side-effect? // Accessing unused instance field on a struct is a noop. Just emit the receiver. if (!used && !field.IsVolatile && !field.IsStatic && fieldAccess.ReceiverOpt.Type.IsVerifierValue()) { EmitExpression(fieldAccess.ReceiverOpt, used: false); return; } Debug.Assert(!field.IsConst || field.ContainingType.SpecialType == SpecialType.System_Decimal, "rewriter should lower constant fields into constant expressions"); if (field.IsStatic) { if (field.IsVolatile) { _builder.EmitOpCode(ILOpCode.Volatile); } _builder.EmitOpCode(ILOpCode.Ldsfld); EmitSymbolToken(field, fieldAccess.Syntax); } else { var receiver = fieldAccess.ReceiverOpt; var fieldType = field.Type; if (fieldType.IsValueType && (object)fieldType == (object)receiver.Type) { //Handle emitting a field of a self-containing struct (only possible in mscorlib) //since "val.field" is the same as val, we only need to emit val. EmitExpression(receiver, used); } else { var temp = EmitFieldLoadReceiver(receiver); if (temp != null) { Debug.Assert(FieldLoadMustUseRef(receiver), "only clr-ambiguous structs use temps here"); FreeTemp(temp); } if (field.IsVolatile) { _builder.EmitOpCode(ILOpCode.Volatile); } _builder.EmitOpCode(ILOpCode.Ldfld); EmitSymbolToken(field, fieldAccess.Syntax); } } EmitPopIfUnused(used); }
public override object VisitFieldAccess(BoundFieldAccess node, object arg) { VisitExpression(node.Receiver); // TODO: special case for instance fields of structs return(null); }