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
}");
}