提交 3fe5a03f 编写于 作者: V vsadov

refs of readonly fields, parameters, returns

上级 e8d146c8
......@@ -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,
}
/// <summary>
......@@ -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:
/// </remarks>
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.
/// </summary>
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;
}
}
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// May introduce a temp which it will return. (otherwise returns null)
/// </summary>
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)
}
/// <summary>
/// Emits receiver in a form that allows member accesses ( O or &amp; ). 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 &amp; ).
/// 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)
/// </summary>
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);
}
/// <summary>
/// May introduce a temp which it will return. (otherwise returns null)
/// </summary>
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);
......
......@@ -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 <MyStruct>
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;
......
......@@ -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);
......
......@@ -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()
......
......@@ -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<int, int> V_2,
int V_3,
System.ValueTuple<int, int> 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<int, int>.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<int, int>.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<int, int>.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<int, int>.Item1""
IL_0038: ret
}");
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册