diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs index d5876d12df76981792db1684886c0e43c53b7f99..d440aa24a848be1b7adac7c24ae294262de254f9 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs @@ -17,8 +17,13 @@ private enum AddressKind // reference may be written to Writeable, - // reference itself will not be written to, but may be used to modify fields. - ReadOnly + // reference itself will not be written to, but may be used for call, callvirt. + // for all purposes it the same as Writeable, except when fetching an address of an array element + // where it results in a ".readonly" prefix to deal with array covariance. + Constrained, + + // reference itself will not be written to, nor it will be used to modify fields. + Readonly, } /// @@ -58,7 +63,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr break; case BoundKind.FieldAccess: - return EmitFieldAddress((BoundFieldAccess)expression); + return EmitFieldAddress((BoundFieldAccess)expression, addressKind); case BoundKind.ArrayAccess: //arrays are covariant, but elements can be written to. @@ -95,13 +100,16 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr case BoundKind.Call: var call = (BoundCall)expression; - if (call.Method.RefKind == RefKind.None) + var methodRefKind = call.Method.RefKind; + + if (methodRefKind == RefKind.Ref || + (addressKind == AddressKind.Readonly && methodRefKind == RefKind.RefReadOnly)) { - goto default; + EmitCallExpression(call, UseKind.UsedAsAddress); + break; } - EmitCallExpression(call, UseKind.UsedAsAddress); - break; + goto default; case BoundKind.ConditionalOperator: var conditional = (BoundConditionalOperator)expression; @@ -110,7 +118,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr goto default; } - EmitConditionalOperatorAddress(conditional); + EmitConditionalOperatorAddress(conditional, addressKind); break; case BoundKind.AssignmentOperator: @@ -123,7 +131,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr throw ExceptionUtilities.UnexpectedValue(assignment.RefKind); default: - Debug.Assert(!HasHome(expression)); + Debug.Assert(!HasHome(expression, addressKind != AddressKind.Readonly)); return EmitAddressOfTempClone(expression); } @@ -143,7 +151,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr /// push x /// DONE: /// - private void EmitConditionalOperatorAddress(BoundConditionalOperator expr) + private void EmitConditionalOperatorAddress(BoundConditionalOperator expr, AddressKind addressKind) { Debug.Assert(expr.ConstantValue == null, "Constant value should have been emitted directly"); @@ -151,7 +159,7 @@ private void EmitConditionalOperatorAddress(BoundConditionalOperator expr) object doneLabel = new object(); EmitCondBranch(expr.Condition, ref consequenceLabel, sense: true); - var temp = EmitAddress(expr.Alternative, AddressKind.Writeable); + var temp = EmitAddress(expr.Alternative, addressKind); Debug.Assert(temp == null); _builder.EmitBranch(ILOpCode.Br, doneLabel); @@ -160,7 +168,7 @@ private void EmitConditionalOperatorAddress(BoundConditionalOperator expr) _builder.AdjustStack(-1); _builder.MarkLabel(consequenceLabel); - EmitAddress(expr.Consequence, AddressKind.Writeable); + EmitAddress(expr.Consequence, addressKind); _builder.MarkLabel(doneLabel); } @@ -180,13 +188,14 @@ private void EmitComplexConditionalReceiverAddress(BoundComplexConditionalReceiv EmitBox(receiverType, expression.Syntax); _builder.EmitBranch(ILOpCode.Brtrue, whenValueTypeLabel); - var receiverTemp = EmitAddress(expression.ReferenceTypeReceiver, addressKind: AddressKind.ReadOnly); + var receiverTemp = EmitAddress(expression.ReferenceTypeReceiver, AddressKind.Readonly); Debug.Assert(receiverTemp == null); _builder.EmitBranch(ILOpCode.Br, doneLabel); _builder.AdjustStack(-1); _builder.MarkLabel(whenValueTypeLabel); - EmitReceiverRef(expression.ValueTypeReceiver, isAccessConstrained: true); + // we will not write through this receiver, but it could be a target of mutating calls + EmitReceiverRef(expression.ValueTypeReceiver, AddressKind.Constrained); _builder.MarkLabel(doneLabel); } @@ -325,11 +334,10 @@ private LocalSymbol DigForValueLocal(BoundSequence topSequence, BoundExpression /// Checks if expression directly or indirectly represents a value with its own home. In /// such cases it is possible to get a reference without loading into a temporary. /// - private bool HasHome(BoundExpression expression) + private bool HasHome(BoundExpression expression, bool needWriteable) { switch (expression.Kind) { - case BoundKind.Parameter: case BoundKind.ArrayAccess: case BoundKind.ThisReference: case BoundKind.BaseReference: @@ -337,61 +345,83 @@ private bool HasHome(BoundExpression expression) case BoundKind.RefValueOperator: return true; + case BoundKind.Parameter: + return !needWriteable || + ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.RefReadOnly; + case BoundKind.Local: // locals have home unless they are byval stack locals var local = ((BoundLocal)expression).LocalSymbol; return !IsStackLocal(local) || local.RefKind != RefKind.None; case BoundKind.Call: - var method = ((BoundCall)expression).Method; - return method.RefKind != RefKind.None; + var methodRefKind = ((BoundCall)expression).Method.RefKind; + return methodRefKind == RefKind.Ref || + (!needWriteable && methodRefKind == RefKind.RefReadOnly); case BoundKind.Dup: + //PROTOTYPE(readonlyRefs): makes sure readonly variables are not duped and written to return ((BoundDup)expression).RefKind != RefKind.None; case BoundKind.FieldAccess: - return HasHome((BoundFieldAccess)expression); + return HasHome((BoundFieldAccess)expression, needWriteable); case BoundKind.Sequence: - return HasHome(((BoundSequence)expression).Value); + return HasHome(((BoundSequence)expression).Value, needWriteable); case BoundKind.AssignmentOperator: return ((BoundAssignmentOperator)expression).RefKind != RefKind.None; case BoundKind.ComplexConditionalReceiver: - Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ValueTypeReceiver)); - Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ReferenceTypeReceiver)); + Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ValueTypeReceiver, needWriteable)); + Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ReferenceTypeReceiver, needWriteable)); goto case BoundKind.ConditionalReceiver; case BoundKind.ConditionalReceiver: + //PROTOTYPE(readonlyRefs): are these always writeable? Test coverage? return true; + case BoundKind.ConditionalOperator: + //PROTOTYPE(readonlyRefs): Test coverage for in-place assignment/init. + return ((BoundConditionalOperator)expression).IsByRef; + default: return false; } } /// - /// Special HasHome for fields. Fields have homes when they are writable. + /// 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. /// - private bool HasHome(BoundFieldAccess fieldAccess) + private bool HasHome(BoundFieldAccess fieldAccess, bool needWriteable) { - // Some field accesses must be values; values do not have homes. - if (fieldAccess.IsByValue) + FieldSymbol field = fieldAccess.FieldSymbol; + + // const fields are literal values with no homes + if (field.IsConst) { + //PROTOTYPE(readonlyRefs): does this actually happen? return false; } - FieldSymbol field = fieldAccess.FieldSymbol; + if (!needWriteable) + { + return true; + } - // const fields are literal values with no homes - if (field.IsConst) + // Some field accesses must be values; values do not have homes. + if (fieldAccess.IsByValue) { return false; } if (!field.IsReadOnly) { + //PROTOTYPE(readonlyRefs): should we dig through struct receivers? + // roField.a.b.c.d.Method() // roField is readonly, all structs + // is it cheaper to copy "d" than "roField", but getting rw ref of roField could upset verifier return true; } @@ -427,7 +457,7 @@ private void EmitArrayElementAddress(BoundArrayAccess arrayAccess, AddressKind a EmitExpression(arrayAccess.Expression, used: true); EmitArrayIndices(arrayAccess.Indices); - if (addressKind == AddressKind.ReadOnly) + if (addressKind == AddressKind.Constrained) { Debug.Assert(arrayAccess.Type.TypeKind == TypeKind.TypeParameter, ".readonly is only needed when element type is a type param"); @@ -451,11 +481,11 @@ private void EmitArrayElementAddress(BoundArrayAccess arrayAccess, AddressKind a /// /// May introduce a temp which it will return. (otherwise returns null) /// - private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess) + private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess, AddressKind addressKind) { FieldSymbol field = fieldAccess.FieldSymbol; - if (!HasHome(fieldAccess)) + if (!HasHome(fieldAccess, addressKind != AddressKind.Readonly)) { // accessing a field that is not writable (const or readonly) return EmitAddressOfTempClone(fieldAccess); @@ -467,7 +497,7 @@ private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess) } else { - return EmitInstanceFieldAddress(fieldAccess); + return EmitInstanceFieldAddress(fieldAccess, isReadonly: addressKind == AddressKind.Readonly); } } @@ -491,16 +521,16 @@ private void EmitParameterAddress(BoundParameter parameter) } /// - /// Emits receiver in a form that allows member accesses ( O or & ). For verifiably - /// reference types it is the actual reference. For generic types it is a address of the - /// receiver with readonly intent. For the value types it is an address of the receiver. + /// Emits receiver in a form that allows member accesses ( O or & ). + /// For verifiably reference types it is the actual reference. + /// For the value types it is an address of the receiver. + /// For generic types it is either a boxed receiver or the address of the receiver with readonly intent. /// - /// isAccessConstrained indicates that receiver is a target of a constrained callvirt - /// in such case it is unnecessary to box a receiver that is typed to a type parameter + /// addressKind - kind of address that is needed in case if receiver is not a reference type. /// /// May introduce a temp which it will return. (otherwise returns null) /// - private LocalDefinition EmitReceiverRef(BoundExpression receiver, bool isAccessConstrained = false) + private LocalDefinition EmitReceiverRef(BoundExpression receiver, AddressKind addressKind) { var receiverType = receiver.Type; if (receiverType.IsVerifierReference()) @@ -518,9 +548,9 @@ private LocalDefinition EmitReceiverRef(BoundExpression receiver, bool isAccessC //via the generic parameter unless it is first boxed (see Partition III) or //the callvirt instruction is prefixed with the constrained. prefix instruction //(see Partition III). end note] - if (isAccessConstrained) + if (addressKind == AddressKind.Constrained) { - return EmitAddress(receiver, AddressKind.ReadOnly); + return EmitAddress(receiver, addressKind); } else { @@ -535,17 +565,17 @@ private LocalDefinition EmitReceiverRef(BoundExpression receiver, bool isAccessC } Debug.Assert(receiverType.IsVerifierValue()); - return EmitAddress(receiver, AddressKind.Writeable); + return EmitAddress(receiver, addressKind); } /// /// May introduce a temp which it will return. (otherwise returns null) /// - private LocalDefinition EmitInstanceFieldAddress(BoundFieldAccess fieldAccess) + private LocalDefinition EmitInstanceFieldAddress(BoundFieldAccess fieldAccess, bool isReadonly) { var field = fieldAccess.FieldSymbol; - var tempOpt = EmitReceiverRef(fieldAccess.ReceiverOpt); + var tempOpt = EmitReceiverRef(fieldAccess.ReceiverOpt, isReadonly? AddressKind.Readonly: AddressKind.Writeable); _builder.EmitOpCode(ILOpCode.Ldflda); EmitSymbolToken(field, fieldAccess.Syntax); diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index c9fc9a06ad5db249d35d271e13d2ca1d94e0a3c4..4d13f5317b77a4befcbe4f8092fd287c73ff21c3 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -359,8 +359,10 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces var receiverConstant = receiver.ConstantValue; if (receiverConstant != null) { - // const but not default - receiverTemp = EmitReceiverRef(receiver, isAccessConstrained: !receiverType.IsReferenceType); + // const but not default, must be a reference type + Debug.Assert(receiverType.IsVerifierReference()); + // receiver is a reference type, so addresskind does not matter, but we do not intend to write. + receiverTemp = EmitReceiverRef(receiver, AddressKind.Readonly); EmitExpression(expression.WhenNotNull, used); if (receiverTemp != null) { @@ -374,7 +376,7 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces object doneLabel = new object(); LocalDefinition cloneTemp = null; - var unconstrainedReceiver = !receiverType.IsReferenceType && !receiverType.IsValueType; + var notConstrained = !receiverType.IsReferenceType && !receiverType.IsValueType; // we need a copy if we deal with nonlocal value (to capture the value) // or if we have a ref-constrained T (to do box just once) @@ -382,14 +384,17 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces // or if we have default(T) (to do box just once) var nullCheckOnCopy = LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) || (receiverType.IsReferenceType && receiverType.TypeKind == TypeKind.TypeParameter) || - (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)); + (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)) || + (receiver.IsDefaultValue() && notConstrained); // ===== RECEIVER if (nullCheckOnCopy) { - receiverTemp = EmitReceiverRef(receiver, isAccessConstrained: unconstrainedReceiver); - if (unconstrainedReceiver) + if (notConstrained) { + // if T happens to be a value type, it could be a target of mutating calls. + receiverTemp = EmitReceiverRef(receiver, AddressKind.Constrained); + // unconstrained case needs to handle case where T is actually a struct. // such values are never nulls // we will emit a check for such case, but the check is really a JIT-time @@ -419,13 +424,18 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces } else { + //PROTOTYPE(readonlyRefs): this does not need to be writeable + // we may call "HasValue" on this, but it is not mutating + receiverTemp = EmitReceiverRef(receiver, AddressKind.Writeable); _builder.EmitOpCode(ILOpCode.Dup); // here we have loaded two copies of a reference { O, O } or {&nub, &nub} } } else { - receiverTemp = EmitReceiverRef(receiver, isAccessConstrained: false); + //PROTOTYPE(readonlyRefs): this does not need to be writeable + // we may call "HasValue" on this, but it is not mutating + receiverTemp = EmitReceiverRef(receiver, AddressKind.Writeable); // here we have loaded just { O } or {&nub} // we have the most trivial case where we can just reload receiver when needed again } @@ -490,8 +500,9 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces if (!nullCheckOnCopy) { Debug.Assert(receiverTemp == null); - receiverTemp = EmitReceiverRef(receiver, isAccessConstrained: unconstrainedReceiver); - Debug.Assert(receiverTemp == null || receiver.IsDefaultValue()); + // receiver may be used as target of a struct call (if T happens to be a sruct) + receiverTemp = EmitReceiverRef(receiver, AddressKind.Constrained); + Debug.Assert(receiverTemp == null); } EmitExpression(expression.WhenNotNull, used); @@ -583,8 +594,7 @@ private void EmitArgument(BoundExpression argument, RefKind refKind) case RefKind.RefReadOnly: //PROTOTYPE(reaadonlyRefs): leaking a temp here - //PROTOTYPE(reaadonlyRefs): readonly fields should not be cloned to temps - var temp = EmitAddress(argument, AddressKind.Writeable); + var temp = EmitAddress(argument, AddressKind.Readonly); break; default: @@ -641,8 +651,8 @@ private void EmitDupExpression(BoundDup expression, bool used) private void EmitDelegateCreationExpression(BoundDelegateCreationExpression expression, bool used) { - Debug.Assert(expression.Argument?.Kind != BoundKind.MethodGroup); - var receiver = expression.Argument; + var mg = expression.Argument as BoundMethodGroup; + var receiver = mg != null ? mg.ReceiverOpt : expression.Argument; var meth = expression.MethodOpt ?? receiver.Type.DelegateInvokeMethod(); Debug.Assert((object)meth != null); EmitDelegateCreation(expression, receiver, expression.IsExtensionMethod, meth, expression.Type, used); @@ -938,7 +948,7 @@ private LocalDefinition EmitFieldLoadReceiver(BoundExpression receiver) // there are also cases where we must emit receiver as a reference if (FieldLoadMustUseRef(receiver) || FieldLoadPrefersRef(receiver)) { - return EmitFieldLoadReceiverAddress(receiver) ? null : EmitReceiverRef(receiver); + return EmitFieldLoadReceiverAddress(receiver) ? null : EmitReceiverRef(receiver, AddressKind.Readonly); } EmitExpression(receiver, true); @@ -1013,7 +1023,9 @@ private bool FieldLoadPrefersRef(BoundExpression receiver) } // can we take address at all? - if (!HasHome(receiver)) + //PROTOTYPE(readonlyRefs): we only need to read, so we could pass "false" here + // but that may result in getting a ref off a readonly field, which could upset verifier + if (!HasHome(receiver, needWriteable: true)) { return false; } @@ -1277,7 +1289,7 @@ private bool CanUseCallOnRefTypeReceiver(BoundExpression receiver) case ConversionKind.MethodGroup: case ConversionKind.AnonymousFunction: - throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); + return true; case ConversionKind.ExplicitReference: case ConversionKind.ImplicitReference: @@ -1364,7 +1376,7 @@ private void EmitCallExpression(BoundCall call, UseKind useKind) Debug.Assert(method.ContainingType == receiver.Type); Debug.Assert(receiver.Kind == BoundKind.ThisReference); - tempOpt = EmitReceiverRef(receiver); + tempOpt = EmitReceiverRef(receiver, AddressKind.Writeable); _builder.EmitOpCode(ILOpCode.Initobj); // initobj EmitSymbolToken(method.ContainingType, call.Syntax); FreeOptTemp(tempOpt); @@ -1386,7 +1398,7 @@ private void EmitCallExpression(BoundCall call, UseKind useKind) if (receiverType.IsVerifierReference()) { - tempOpt = EmitReceiverRef(receiver, isAccessConstrained: false); + EmitExpression(receiver, used: true); // In some cases CanUseCallOnRefTypeReceiver returns true which means that // null check is unnecessary and we can use "call" @@ -1411,7 +1423,9 @@ private void EmitCallExpression(BoundCall call, UseKind useKind) // calling a method defined in a value type Debug.Assert(receiverType == methodContainingType); - tempOpt = EmitReceiverRef(receiver); + // method is defined in the struct itself and is assumed to be mutating. + //PROTOTYPE(readonlyRefs): when readonly structs are implemented, this will have to check "this" + tempOpt = EmitReceiverRef(receiver, AddressKind.Writeable); callKind = CallKind.Call; } else @@ -1420,7 +1434,11 @@ private void EmitCallExpression(BoundCall call, UseKind useKind) { // When calling a method that is virtual in metadata on a struct receiver, // we use a constrained virtual call. If possible, it will skip boxing. - tempOpt = EmitReceiverRef(receiver, isAccessConstrained: true); + // + //PROTOTYPE(readonlyRefs): all methods that a struct could inherit from bases are non-mutating + // we are passing here "Writeable" just to keep verifier happy + // we should pass here "Readonly" and avoid unnecessary copy + tempOpt = EmitReceiverRef(receiver, AddressKind.Writeable); callKind = CallKind.ConstrainedCallVirt; } else @@ -1441,7 +1459,7 @@ private void EmitCallExpression(BoundCall call, UseKind useKind) CallKind.CallVirt : CallKind.ConstrainedCallVirt; - tempOpt = EmitReceiverRef(receiver, isAccessConstrained: callKind == CallKind.ConstrainedCallVirt); + tempOpt = EmitReceiverRef(receiver, callKind == CallKind.ConstrainedCallVirt ? AddressKind.Constrained : AddressKind.Writeable); } } @@ -1945,7 +1963,7 @@ private bool TryEmitAssignmentInPlace(BoundAssignmentOperator assignmentOperator private bool SafeToGetWriteableReference(BoundExpression left) { - if (!HasHome(left)) + if (!HasHome(left, needWriteable: true)) { return false; } @@ -2068,7 +2086,7 @@ private bool EmitAssignmentPreamble(BoundAssignmentOperator assignmentOperator) var left = (BoundFieldAccess)assignmentTarget; if (!left.FieldSymbol.IsStatic) { - var temp = EmitReceiverRef(left.ReceiverOpt); + var temp = EmitReceiverRef(left.ReceiverOpt, AddressKind.Writeable); Debug.Assert(temp == null, "temp is unexpected when assigning to a field"); lhsUsesStack = true; } @@ -3035,9 +3053,9 @@ private TypeSymbol StackMergeType(BoundExpression expr) var conversion = (BoundConversion)expr; var conversionKind = conversion.ConversionKind; if (conversionKind.IsImplicitConversion() && + conversionKind != ConversionKind.MethodGroup && conversionKind != ConversionKind.NullLiteral) { - Debug.Assert(conversionKind != ConversionKind.MethodGroup); return StackMergeType(conversion.Operand); } break; diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs index 332d3eea47de6444cf2601901935b2d77dc38b9e..a4f47b50bb2fa6626840e9f478981dfb403d0d36 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs @@ -487,7 +487,8 @@ private void EmitCondBranchCore(BoundExpression condition, ref object dest, bool object fallThrough = null; EmitCondBranch(receiver, ref fallThrough, sense: false); - EmitReceiverRef(receiver, isAccessConstrained: false); + // receiver is a reference type, and we only intend to read it + EmitReceiverRef(receiver, AddressKind.Readonly); EmitCondBranch(ca.WhenNotNull, ref dest, sense: true); if (fallThrough != null) @@ -500,7 +501,8 @@ private void EmitCondBranchCore(BoundExpression condition, ref object dest, bool // gotoif(receiver == null) labDest // gotoif(!receiver.Access) labDest EmitCondBranch(receiver, ref dest, sense: false); - EmitReceiverRef(receiver, isAccessConstrained: false); + // receiver is a reference type, and we only intend to read it + EmitReceiverRef(receiver, AddressKind.Readonly); condition = ca.WhenNotNull; goto oneMoreTime; } @@ -716,7 +718,7 @@ private void EmitReturnStatement(BoundReturnStatement boundReturnStatement) } else { - this.EmitAddress(expressionOpt, AddressKind.Writeable); + this.EmitAddress(expressionOpt, this._method.RefKind == RefKind.RefReadOnly? AddressKind.Readonly: AddressKind.Writeable); } if (ShouldUseIndirectReturn()) @@ -1012,7 +1014,7 @@ private void EmitCatchBlock(BoundCatchBlock catchBlock) var temp = AllocateTemp(exceptionSource.Type, exceptionSource.Syntax); _builder.EmitLocalStore(temp); - var receiverTemp = EmitReceiverRef(left.ReceiverOpt); + var receiverTemp = EmitReceiverRef(left.ReceiverOpt, AddressKind.Writeable); Debug.Assert(receiverTemp == null); _builder.EmitLocalLoad(temp); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs index a4df2daf1cc1faf3fb9ebf707485e195a9c784fa..14b2c3b162d16afd1d93a2ba1d4683211e176bd0 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs @@ -109,6 +109,86 @@ .locals init (int V_0) IL_0010: ret }"); } + + [Fact] + public void InParamPassRoField() + { + var text = @" +class Program +{ + public static readonly int F = 42; + + public static void Main() + { + System.Console.WriteLine(M(F)); + } + + static ref readonly int M(in int x) + { + return ref x; + } +} +"; + + var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: false, expectedOutput: "42"); + + comp.VerifyIL("Program.Main()", @" +{ + // Code size 17 (0x11) + .maxstack 1 + IL_0000: ldsflda ""int Program.F"" + IL_0005: call ""ref readonly int Program.M(ref readonly int)"" + IL_000a: ldind.i4 + IL_000b: call ""void System.Console.WriteLine(int)"" + IL_0010: ret +}"); + + + comp.VerifyIL("Program.M(ref readonly int)", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ret +}"); + } + + [Fact] + public void InParamPassRoParamReturn() + { + var text = @" +class Program +{ + public static readonly int F = 42; + + public static void Main() + { + System.Console.WriteLine(M(F)); + } + + static ref readonly int M(in int x) + { + return ref M1(x); + } + + static ref readonly int M1(in int x) + { + return ref x; + } +} +"; + + var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: false, expectedOutput: "42"); + + comp.VerifyIL("Program.M(ref readonly int)", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""ref readonly int Program.M1(ref readonly int)"" + IL_0006: ret +}"); + } [Fact] public void RefReturnParamAccess1() diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs index cff00b55e6115c2320f7e9418a1e4a2c15f5d753..073f9ffd5a1e908591f663bf52362dc2c4caa5fc 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs @@ -459,46 +459,33 @@ struct S var comp = CompileAndVerify(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular, verify: false); - //PROTOTYPE(readonlyRef): correct emit is NYI. We should not make copies when returning r/o fields comp.VerifyIL("Program.Test", @" { - // Code size 70 (0x46) + // Code size 57 (0x39) .maxstack 1 - .locals init (bool V_0, //b - int V_1, - System.ValueTuple V_2, - int V_3, - System.ValueTuple V_4) + .locals init (bool V_0) //b IL_0000: ldc.i4.1 IL_0001: stloc.0 IL_0002: ldloc.0 - IL_0003: brfalse.s IL_0020 + IL_0003: brfalse.s IL_001a IL_0005: ldloc.0 - IL_0006: brfalse.s IL_0012 + IL_0006: brfalse.s IL_000f IL_0008: ldarg.0 - IL_0009: ldfld ""int Program.F"" - IL_000e: stloc.1 - IL_000f: ldloca.s V_1 - IL_0011: ret - IL_0012: ldsfld ""(int Alice, int Bob) Program.F1"" - IL_0017: stloc.2 - IL_0018: ldloca.s V_2 - IL_001a: ldflda ""int System.ValueTuple.Item1"" - IL_001f: ret - IL_0020: ldloc.0 - IL_0021: brfalse.s IL_0032 - IL_0023: ldarg.0 - IL_0024: ldfld ""Program.S Program.S1"" - IL_0029: ldfld ""int Program.S.F"" - IL_002e: stloc.3 - IL_002f: ldloca.s V_3 - IL_0031: ret - IL_0032: ldsfld ""Program.S Program.S2"" - IL_0037: ldfld ""(int Alice, int Bob) Program.S.F1"" - IL_003c: stloc.s V_4 - IL_003e: ldloca.s V_4 - IL_0040: ldflda ""int System.ValueTuple.Item1"" - IL_0045: ret + IL_0009: ldflda ""int Program.F"" + IL_000e: ret + IL_000f: ldsflda ""(int Alice, int Bob) Program.F1"" + IL_0014: ldflda ""int System.ValueTuple.Item1"" + IL_0019: ret + IL_001a: ldloc.0 + IL_001b: brfalse.s IL_0029 + IL_001d: ldarg.0 + IL_001e: ldflda ""Program.S Program.S1"" + IL_0023: ldflda ""int Program.S.F"" + IL_0028: ret + IL_0029: ldsflda ""Program.S Program.S2"" + IL_002e: ldflda ""(int Alice, int Bob) Program.S.F1"" + IL_0033: ldflda ""int System.ValueTuple.Item1"" + IL_0038: ret }"); }