提交 ab63bff3 编写于 作者: V Vladimir Sadov 提交者: Julien Couvreur

Enable peverify-compat mode for langver < 7.2 (#22772)

上级 289aebf0
......@@ -139,7 +139,7 @@ private bool IsDebugPlus()
private bool EnablePEVerifyCompat()
{
return _module.Compilation.FeaturePEVerifyCompatEnabled;
return _module.Compilation.LanguageVersion < LanguageVersion.CSharp7_2 || _module.Compilation.FeaturePEVerifyCompatEnabled;
}
private LocalDefinition LazyReturnTemp
......
......@@ -24,8 +24,14 @@ private enum AddressKind
// reference itself will not be written to, nor it will be used to modify fields.
ReadOnly,
// same as ReadOnly, but we are not supposed to get a reference to a clone
// regardless of compat settings.
ReadOnlyStrict,
}
private static bool IsReadOnly(AddressKind addressKind) => addressKind >= AddressKind.ReadOnly;
/// <summary>
/// Emits address as in &amp;
///
......@@ -70,7 +76,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
case BoundKind.ThisReference:
Debug.Assert(expression.Type.IsValueType, "only value types may need a ref to this");
Debug.Assert(HasHome(expression, addressKind == AddressKind.Writeable));
Debug.Assert(HasHome(expression, addressKind));
_builder.EmitOpCode(ILOpCode.Ldarg_0);
break;
......@@ -101,7 +107,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
var methodRefKind = call.Method.RefKind;
if (methodRefKind == RefKind.Ref ||
(addressKind == AddressKind.ReadOnly && methodRefKind == RefKind.RefReadOnly))
(IsReadOnly(addressKind) && methodRefKind == RefKind.RefReadOnly))
{
EmitCallExpression(call, UseKind.UsedAsAddress);
break;
......@@ -121,7 +127,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expression;
if (!HasHome(conditional, addressKind != AddressKind.ReadOnly))
if (!HasHome(conditional, addressKind))
{
goto default;
}
......@@ -144,7 +150,7 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
return null;
default:
Debug.Assert(!HasHome(expression, addressKind != AddressKind.ReadOnly));
Debug.Assert(!HasHome(expression, addressKind));
return EmitAddressOfTempClone(expression);
}
......@@ -218,7 +224,7 @@ private LocalDefinition EmitLocalAddress(BoundLocal localAccess, AddressKind add
{
var local = localAccess.LocalSymbol;
if (!HasHome(localAccess, needWriteable: addressKind != AddressKind.ReadOnly))
if (!HasHome(localAccess, addressKind))
{
return EmitAddressOfTempClone(localAccess);
}
......@@ -249,7 +255,7 @@ private LocalDefinition EmitLocalAddress(BoundLocal localAccess, AddressKind add
/// </summary>
private LocalDefinition EmitDupAddress(BoundDup dup, AddressKind addressKind)
{
if (!HasHome(dup, needWriteable: addressKind != AddressKind.ReadOnly))
if (!HasHome(dup, addressKind))
{
return EmitAddressOfTempClone(dup);
}
......@@ -339,7 +345,7 @@ 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, bool needWriteable)
private bool HasHome(BoundExpression expression, AddressKind addressKind)
{
switch (expression.Kind)
{
......@@ -352,10 +358,11 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
case BoundKind.ThisReference:
Debug.Assert(expression.Type.IsValueType);
if (needWriteable && expression.Type.IsReadOnly)
if (!IsReadOnly(addressKind) && expression.Type.IsReadOnly)
{
return _method.MethodKind == MethodKind.Constructor;
}
return true;
case BoundKind.ThrowExpression:
......@@ -363,7 +370,7 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
return true;
case BoundKind.Parameter:
return !needWriteable ||
return IsReadOnly(addressKind) ||
((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.In;
case BoundKind.Local:
......@@ -371,31 +378,31 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
// locals in a mutating call
var local = ((BoundLocal)expression).LocalSymbol;
return !((IsStackLocal(local) && local.RefKind == RefKind.None) ||
(needWriteable && local.RefKind == RefKind.RefReadOnly));
(!IsReadOnly(addressKind) && local.RefKind == RefKind.RefReadOnly));
case BoundKind.Call:
var methodRefKind = ((BoundCall)expression).Method.RefKind;
return methodRefKind == RefKind.Ref ||
(!needWriteable && methodRefKind == RefKind.RefReadOnly);
(IsReadOnly(addressKind) && methodRefKind == RefKind.RefReadOnly);
case BoundKind.Dup:
//NB: Dup represents locals that do not need IL slot
var dupRefKind = ((BoundDup)expression).RefKind;
return dupRefKind == RefKind.Ref ||
(!needWriteable && dupRefKind == RefKind.RefReadOnly);
(IsReadOnly(addressKind) && dupRefKind == RefKind.RefReadOnly);
case BoundKind.FieldAccess:
return HasHome((BoundFieldAccess)expression, needWriteable);
return HasHome((BoundFieldAccess)expression, addressKind);
case BoundKind.Sequence:
return HasHome(((BoundSequence)expression).Value, needWriteable);
return HasHome(((BoundSequence)expression).Value, addressKind);
case BoundKind.AssignmentOperator:
return ((BoundAssignmentOperator)expression).RefKind != RefKind.None;
case BoundKind.ComplexConditionalReceiver:
Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ValueTypeReceiver, needWriteable));
Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ReferenceTypeReceiver, needWriteable));
Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ValueTypeReceiver, addressKind));
Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ReferenceTypeReceiver, addressKind));
goto case BoundKind.ConditionalReceiver;
case BoundKind.ConditionalReceiver:
......@@ -415,7 +422,7 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
// branch that has no home will need a temporary
// if both have no home, just say whole expression has no home
// so we could just use one temp for the whole thing
return HasHome(ternary.Consequence, needWriteable) && HasHome(ternary.Alternative, needWriteable);
return HasHome(ternary.Consequence, addressKind) && HasHome(ternary.Alternative, addressKind);
default:
return false;
......@@ -427,7 +434,7 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
/// 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, bool needWriteable)
private bool HasHome(BoundFieldAccess fieldAccess, AddressKind addressKind)
{
FieldSymbol field = fieldAccess.FieldSymbol;
......@@ -437,7 +444,14 @@ private bool HasHome(BoundFieldAccess fieldAccess, bool needWriteable)
return false;
}
if (!needWriteable && !EnablePEVerifyCompat())
// 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;
}
......@@ -456,7 +470,7 @@ private bool HasHome(BoundFieldAccess fieldAccess, bool needWriteable)
// 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(needWriteable == true);
Debug.Assert(!IsReadOnly(addressKind));
var receiver = fieldAccess.ReceiverOpt;
if (receiver?.Type.IsValueType == true)
......@@ -466,8 +480,8 @@ private bool HasHome(BoundFieldAccess fieldAccess, bool needWriteable)
// 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, needWriteable: true) ||
!HasHome(receiver, needWriteable: false);
return HasHome(receiver, addressKind) ||
!HasHome(receiver, AddressKind.ReadOnly);
}
}
......@@ -534,7 +548,7 @@ private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess, AddressKi
{
FieldSymbol field = fieldAccess.FieldSymbol;
if (!HasHome(fieldAccess, addressKind != AddressKind.ReadOnly))
if (!HasHome(fieldAccess, addressKind))
{
// accessing a field that is not writable (const or readonly)
return EmitAddressOfTempClone(fieldAccess);
......@@ -546,11 +560,7 @@ private LocalDefinition EmitFieldAddress(BoundFieldAccess fieldAccess, AddressKi
}
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);
return EmitInstanceFieldAddress(fieldAccess, addressKind);
}
}
......@@ -564,7 +574,7 @@ private LocalDefinition EmitParameterAddress(BoundParameter parameter, AddressKi
{
ParameterSymbol parameterSymbol = parameter.ParameterSymbol;
if (!HasHome(parameter, needWriteable: addressKind != AddressKind.ReadOnly))
if (!HasHome(parameter, addressKind))
{
// accessing a parameter that is not writable
return EmitAddressOfTempClone(parameter);
......@@ -634,11 +644,14 @@ private LocalDefinition EmitReceiverRef(BoundExpression receiver, AddressKind ad
/// <summary>
/// May introduce a temp which it will return. (otherwise returns null)
/// </summary>
private LocalDefinition EmitInstanceFieldAddress(BoundFieldAccess fieldAccess, bool isReadonly)
private LocalDefinition EmitInstanceFieldAddress(BoundFieldAccess fieldAccess, AddressKind addressKind)
{
var field = fieldAccess.FieldSymbol;
var tempOpt = EmitReceiverRef(fieldAccess.ReceiverOpt, isReadonly? AddressKind.ReadOnly: AddressKind.Writeable);
//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);
......
......@@ -426,10 +426,7 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces
{
// this does not need to be writeable
// we may call "HasValue" on this, but it is not mutating
// (however verification does not know that and considers all calls mutating)
var addressKind = EnablePEVerifyCompat()?
AddressKind.Writeable:
AddressKind.ReadOnly;
var addressKind = AddressKind.ReadOnly;
receiverTemp = EmitReceiverRef(receiver, addressKind);
_builder.EmitOpCode(ILOpCode.Dup);
......@@ -605,7 +602,9 @@ private void EmitArgument(BoundExpression argument, RefKind refKind)
break;
default:
var unexpectedTemp = EmitAddress(argument, AddressKind.Writeable);
// NOTE: passing "ReadOnlyStrict" here.
// we should not get an address of a copy if at all possible
var unexpectedTemp = EmitAddress(argument, refKind == RefKindExtensions.StrictIn? AddressKind.ReadOnlyStrict: AddressKind.Writeable);
if (unexpectedTemp != null)
{
// interestingly enough "ref dynamic" sometimes is passed via a clone
......@@ -619,8 +618,11 @@ private void EmitArgument(BoundExpression argument, RefKind refKind)
private void EmitAddressOfExpression(BoundAddressOfOperator expression, bool used)
{
var temp = EmitAddress(expression.Operand, AddressKind.Writeable);
// NOTE: passing "ReadOnlyStrict" here.
// we should not get an address of a copy if at all possible
var temp = EmitAddress(expression.Operand, AddressKind.ReadOnlyStrict);
Debug.Assert(temp == null, "If the operand is addressable, then a temp shouldn't be required.");
if (used)
{
// When computing an address to be used to initialize a fixed-statement variable, we have to be careful
......@@ -811,15 +813,32 @@ private void EmitSideEffects(BoundSequence sequence)
}
}
private void EmitArguments(ImmutableArray<BoundExpression> arguments, ImmutableArray<ParameterSymbol> parameters)
private void EmitArguments(ImmutableArray<BoundExpression> arguments, ImmutableArray<ParameterSymbol> parameters, ImmutableArray<RefKind> refKindsOpt)
{
// We might have an extra argument for the __arglist() of a varargs method.
Debug.Assert(arguments.Length == parameters.Length || arguments.Length == parameters.Length + 1, "argument count must match parameter count");
for (int i = 0; i < arguments.Length; i++)
{
BoundExpression argument = arguments[i];
RefKind refKind = (i == parameters.Length) ? RefKind.None : parameters[i].RefKind;
EmitArgument(argument, refKind);
RefKind refKind;
if (!refKindsOpt.IsDefault && i < refKindsOpt.Length)
{
// if we have an explicit refKind for the given argument, use that
refKind = refKindsOpt[i];
}
else if (i < parameters.Length)
{
// otherwise check the parameter
refKind = parameters[i].RefKind;
}
else
{
// vararg case
Debug.Assert(arguments[i].Kind == BoundKind.ArgListOperator);
refKind = RefKind.None;
}
EmitArgument(arguments[i], refKind);
}
}
......@@ -1068,7 +1087,7 @@ private bool FieldLoadPrefersRef(BoundExpression receiver)
}
// can we take address at all?
if (!HasHome(receiver, needWriteable: false))
if (!HasHome(receiver, AddressKind.ReadOnly))
{
return false;
}
......@@ -1491,13 +1510,8 @@ private void EmitCallExpression(BoundCall call, UseKind useKind)
if (method.IsMetadataVirtual())
{
// NB: all methods that a struct could inherit from bases are non-mutating
// we are passing here "Writeable" just to keep verifier happy
// we can pass here "ReadOnly" and avoid unnecessary copy
var addressKind = EnablePEVerifyCompat() ?
AddressKind.Writeable :
AddressKind.ReadOnly;
tempOpt = EmitReceiverRef(receiver, addressKind);
// treat receiver as ReadOnly
tempOpt = EmitReceiverRef(receiver, AddressKind.ReadOnly);
callKind = CallKind.ConstrainedCallVirt;
}
else
......@@ -1569,7 +1583,7 @@ private void EmitCallExpression(BoundCall call, UseKind useKind)
}
}
EmitArguments(arguments, method.Parameters);
EmitArguments(arguments, method.Parameters, call.ArgumentRefKindsOpt);
int stackBehavior = GetCallStackBehavior(call);
switch (callKind)
{
......@@ -1861,7 +1875,7 @@ private void EmitObjectCreationExpression(BoundObjectCreationExpression expressi
}
else
{
EmitArguments(expression.Arguments, constructor.Parameters);
EmitArguments(expression.Arguments, constructor.Parameters, expression.ArgumentRefKindsOpt);
var stackAdjustment = GetObjCreationStackBehavior(expression);
_builder.EmitOpCode(ILOpCode.Newobj, stackAdjustment);
......@@ -2033,7 +2047,7 @@ private bool TryEmitAssignmentInPlace(BoundAssignmentOperator assignmentOperator
private bool SafeToGetWriteableReference(BoundExpression left)
{
if (!HasHome(left, needWriteable: true))
if (!HasHome(left, AddressKind.Writeable))
{
return false;
}
......@@ -2079,7 +2093,7 @@ private void InPlaceCtorCall(BoundExpression target, BoundObjectCreationExpressi
Debug.Assert(temp == null, "in-place ctor target should not create temps");
var constructor = objCreation.Constructor;
EmitArguments(objCreation.Arguments, constructor.Parameters);
EmitArguments(objCreation.Arguments, constructor.Parameters, objCreation.ArgumentRefKindsOpt);
// -2 to adjust for consumed target address and not produced value.
var stackAdjustment = GetObjCreationStackBehavior(objCreation) - 2;
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment);
......@@ -2335,10 +2349,11 @@ private void EmitAssignmentValue(BoundAssignmentOperator assignmentOperator)
else
{
int exprTempsBefore = _expressionTemps?.Count ?? 0;
var local = ((BoundLocal)assignmentOperator.Left).LocalSymbol;
// NOTE: passing "ReadOnly" here. Assuming we do not have compile errors,
// We should not get an address of a copy, even if the RHS is readonly
LocalDefinition temp = EmitAddress(assignmentOperator.Right, AddressKind.ReadOnly);
// NOTE: passing "ReadOnlyStrict" here.
// we should not get an address of a copy if at all possible
LocalDefinition temp = EmitAddress(assignmentOperator.Right, local.RefKind == RefKind.RefReadOnly ? AddressKind.ReadOnlyStrict : AddressKind.Writeable);
// Generally taking a ref for the purpose of ref assignment should not be done on homeless values
// however, there are very rare cases when we need to get a ref off a temp in synthetic code.
......@@ -2346,7 +2361,7 @@ private void EmitAssignmentValue(BoundAssignmentOperator assignmentOperator)
AddExpressionTemp(temp);
// are we, by the way, ref-assigning to something that lives longer than encompassing expression?
if (((BoundLocal)assignmentOperator.Left).LocalSymbol.SynthesizedKind.IsLongLived())
if (local.SynthesizedKind.IsLongLived())
{
var exprTempsAfter = _expressionTemps?.Count ?? 0;
......
......@@ -724,7 +724,10 @@ private void EmitReturnStatement(BoundReturnStatement boundReturnStatement)
}
else
{
this.EmitAddress(expressionOpt, this._method.RefKind == RefKind.RefReadOnly? AddressKind.ReadOnly: AddressKind.Writeable);
// NOTE: passing "ReadOnlyStrict" here.
// we should never return an address of a copy
var unexpectedTemp = this.EmitAddress(expressionOpt, this._method.RefKind == RefKind.RefReadOnly ? AddressKind.ReadOnlyStrict : AddressKind.Writeable);
Debug.Assert(unexpectedTemp == null, "ref-returning a temp?");
}
if (ShouldUseIndirectReturn())
......
......@@ -718,7 +718,6 @@ private static ImmutableArray<RefKind> GetRefKindsOrNull(ArrayBuilder<RefKind> r
if (IsSafeForReordering(argument, argRefKind))
{
arguments[p] = argument;
refKinds[p] = argRefKind;
}
else
{
......@@ -727,6 +726,7 @@ private static ImmutableArray<RefKind> GetRefKindsOrNull(ArrayBuilder<RefKind> r
storesToTemps.Add(assignment);
arguments[p] = temp;
}
refKinds[p] = argRefKind;
}
}
......
......@@ -254,8 +254,10 @@ public override BoundNode VisitFixedLocalCollectionInitializer(BoundFixedLocalCo
pinnedTemp = factory.SynthesizedLocal(
initializerType,
syntax: declarator,
isPinned: true,
refKind: RefKind.Ref, // different from the array and string cases
isPinned: true,
//NOTE: different from the array and string cases
// RefReadOnly to allow referring to readonly variables. (technically we only "read" through the temp anyways)
refKind: RefKind.RefReadOnly,
kind: SynthesizedLocalKind.FixedReference);
// NOTE: we pin the reference, not the pointer.
......
......@@ -1269,7 +1269,7 @@ internal static BoundExpression NullOrDefault(TypeSymbol typeSymbol, SyntaxNode
#endif
)
{
if (refKind == RefKind.Out || refKind == RefKind.In)
if (refKind == RefKind.Out)
{
refKind = RefKind.Ref;
}
......
......@@ -233,6 +233,64 @@ .locals init (int V_0)
}
[Fact]
[CompilerTrait(CompilerFeature.PEVerifyCompat)]
public void InParamPassRoField1()
{
var text = @"
class Program
{
public static readonly int F = 42;
public static void Main()
{
System.Console.WriteLine(M(in 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(in int)""
IL_000a: ldind.i4
IL_000b: call ""void System.Console.WriteLine(int)""
IL_0010: ret
}");
comp.VerifyIL("Program.M(in int)", @"
{
// Code size 2 (0x2)
.maxstack 1
IL_0000: ldarg.0
IL_0001: ret
}");
comp = CompileAndVerify(text, verify: false, expectedOutput: "42", parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature());
comp.VerifyIL("Program.Main()", @"
{
// Code size 17 (0x11)
.maxstack 1
IL_0000: ldsflda ""int Program.F""
IL_0005: call ""ref readonly int Program.M(in int)""
IL_000a: ldind.i4
IL_000b: call ""void System.Console.WriteLine(int)""
IL_0010: ret
}");
}
[Fact]
public void InParamPassRoParamReturn()
{
......@@ -798,6 +856,143 @@ public static void M1(in int arg1, in int arg2, in int arg3)
);
}
[Fact]
public void ReadonlyParamAsyncSpillInRoField()
{
var text = @"
using System.Threading.Tasks;
class Program
{
public static readonly int F = 5;
static void Main(string[] args)
{
Test().Wait();
}
public static async Task Test()
{
int local = 1;
M1(in F, await GetT(2), 3);
}
public static async Task<T> GetT<T>(T val)
{
await Task.Yield();
MutateReadonlyField();
return val;
}
private static unsafe void MutateReadonlyField()
{
fixed(int* ptr = &F)
{
*ptr = 42;
}
}
public static void M1(in int arg1, in int arg2, in int arg3)
{
System.Console.WriteLine(arg1 + arg2 + arg3);
}
}
";
var comp = CreateCompilationWithMscorlib46(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }, options: TestOptions.UnsafeReleaseExe);
var result = CompileAndVerify(comp, verify: false, expectedOutput: @"47");
var expectedIL = @"
{
// Code size 162 (0xa2)
.maxstack 3
.locals init (int V_0,
int V_1,
System.Runtime.CompilerServices.TaskAwaiter<int> V_2,
int V_3,
System.Exception V_4)
IL_0000: ldarg.0
IL_0001: ldfld ""int Program.<Test>d__2.<>1__state""
IL_0006: stloc.0
.try
{
IL_0007: ldloc.0
IL_0008: brfalse.s IL_003f
IL_000a: ldc.i4.2
IL_000b: call ""System.Threading.Tasks.Task<int> Program.GetT<int>(int)""
IL_0010: callvirt ""System.Runtime.CompilerServices.TaskAwaiter<int> System.Threading.Tasks.Task<int>.GetAwaiter()""
IL_0015: stloc.2
IL_0016: ldloca.s V_2
IL_0018: call ""bool System.Runtime.CompilerServices.TaskAwaiter<int>.IsCompleted.get""
IL_001d: brtrue.s IL_005b
IL_001f: ldarg.0
IL_0020: ldc.i4.0
IL_0021: dup
IL_0022: stloc.0
IL_0023: stfld ""int Program.<Test>d__2.<>1__state""
IL_0028: ldarg.0
IL_0029: ldloc.2
IL_002a: stfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Program.<Test>d__2.<>u__1""
IL_002f: ldarg.0
IL_0030: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<Test>d__2.<>t__builder""
IL_0035: ldloca.s V_2
IL_0037: ldarg.0
IL_0038: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted<System.Runtime.CompilerServices.TaskAwaiter<int>, Program.<Test>d__2>(ref System.Runtime.CompilerServices.TaskAwaiter<int>, ref Program.<Test>d__2)""
IL_003d: leave.s IL_00a1
IL_003f: ldarg.0
IL_0040: ldfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Program.<Test>d__2.<>u__1""
IL_0045: stloc.2
IL_0046: ldarg.0
IL_0047: ldflda ""System.Runtime.CompilerServices.TaskAwaiter<int> Program.<Test>d__2.<>u__1""
IL_004c: initobj ""System.Runtime.CompilerServices.TaskAwaiter<int>""
IL_0052: ldarg.0
IL_0053: ldc.i4.m1
IL_0054: dup
IL_0055: stloc.0
IL_0056: stfld ""int Program.<Test>d__2.<>1__state""
IL_005b: ldloca.s V_2
IL_005d: call ""int System.Runtime.CompilerServices.TaskAwaiter<int>.GetResult()""
IL_0062: stloc.1
IL_0063: ldsflda ""int Program.F""
IL_0068: ldloca.s V_1
IL_006a: ldc.i4.3
IL_006b: stloc.3
IL_006c: ldloca.s V_3
IL_006e: call ""void Program.M1(in int, in int, in int)""
IL_0073: leave.s IL_008e
}
catch System.Exception
{
IL_0075: stloc.s V_4
IL_0077: ldarg.0
IL_0078: ldc.i4.s -2
IL_007a: stfld ""int Program.<Test>d__2.<>1__state""
IL_007f: ldarg.0
IL_0080: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<Test>d__2.<>t__builder""
IL_0085: ldloc.s V_4
IL_0087: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)""
IL_008c: leave.s IL_00a1
}
IL_008e: ldarg.0
IL_008f: ldc.i4.s -2
IL_0091: stfld ""int Program.<Test>d__2.<>1__state""
IL_0096: ldarg.0
IL_0097: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<Test>d__2.<>t__builder""
IL_009c: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()""
IL_00a1: ret
}
";
result.VerifyIL("Program.<Test>d__2.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", expectedIL);
comp = CreateCompilationWithMscorlib46(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }, options: TestOptions.UnsafeReleaseExe, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature());
result = CompileAndVerify(comp, verify: false, expectedOutput: @"47");
result.VerifyIL("Program.<Test>d__2.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", expectedIL);
}
[Fact]
public void InParamAsyncSpill2()
{
......
......@@ -54,35 +54,30 @@ class C
}
}";
var comp = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature());
// WithPEVerifyCompatFeature should not cause us to get a ref of a temp in ref assignments
var comp = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: false);
comp.VerifyIL("C.M", @"
{
// Code size 65 (0x41)
// Code size 59 (0x3b)
.maxstack 2
.locals init (S V_0,
S V_1,
S2 V_2)
.locals init (S V_0)
IL_0000: ldsflda ""S C.s1""
IL_0005: dup
IL_0006: ldobj ""S""
IL_000b: stloc.0
IL_000c: ldloca.s V_0
IL_000e: call ""void S.AddOne()""
IL_0013: ldsfld ""S C.s2""
IL_0018: stloc.0
IL_0019: ldloca.s V_0
IL_001b: ldobj ""S""
IL_0020: stloc.1
IL_0021: ldloca.s V_1
IL_0023: call ""void S.AddOne()""
IL_0028: ldsflda ""S2 C.s3""
IL_002d: call ""void S2.AddOne()""
IL_0032: ldarg.0
IL_0033: ldfld ""S2 C.s4""
IL_0038: stloc.2
IL_0039: ldloca.s V_2
IL_003b: call ""void S2.AddOne()""
IL_0040: ret
IL_0013: ldsflda ""S C.s2""
IL_0018: ldobj ""S""
IL_001d: stloc.0
IL_001e: ldloca.s V_0
IL_0020: call ""void S.AddOne()""
IL_0025: ldsflda ""S2 C.s3""
IL_002a: call ""void S2.AddOne()""
IL_002f: ldarg.0
IL_0030: ldflda ""S2 C.s4""
IL_0035: call ""void S2.AddOne()""
IL_003a: ret
}");
comp = CompileAndVerify(source, verify: false);
......@@ -864,47 +859,36 @@ .maxstack 1
IL_0033: ldflda ""int System.ValueTuple<int, int>.Item1""
IL_0038: ret
}");
comp = CompileAndVerify(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: false);
// WithPEVerifyCompatFeature should not cause us to get a ref of a temp in ref returns
comp = CompileAndVerify(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithPEVerifyCompatFeature(), verify: false);
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
}");
}
......
......@@ -12025,7 +12025,7 @@ public MyManagedStruct(int x)
n.n.num = x;
}
}";
var comp = CompileAndVerify(source, expectedOutput: @"42", verify: false);
var comp = CompileAndVerify(source, expectedOutput: @"42", parseOptions: TestOptions.Regular7_2, verify: false);
comp.VerifyIL("Program.Main",
@"
......@@ -12088,6 +12088,38 @@ .locals init (MyManagedStruct V_0)
IL_0046: call ""void System.Console.WriteLine(int)""
IL_004b: ret
}
");
comp = CompileAndVerify(source, expectedOutput: @"42", verify: true, parseOptions: TestOptions.Regular7_1);
comp.VerifyIL("Program.Main",
@"
{
// Code size 76 (0x4c)
.maxstack 3
.locals init (MyManagedStruct V_0)
IL_0000: newobj ""cls1..ctor()""
IL_0005: dup
IL_0006: ldfld ""MyManagedStruct cls1.y""
IL_000b: stloc.0
IL_000c: ldloca.s V_0
IL_000e: ldc.i4.s 123
IL_0010: call ""void MyManagedStruct.mutate(int)""
IL_0015: dup
IL_0016: ldfld ""MyManagedStruct cls1.y""
IL_001b: stloc.0
IL_001c: ldloca.s V_0
IL_001e: ldflda ""MyManagedStruct.Nested MyManagedStruct.n""
IL_0023: ldflda ""MyManagedStruct.Nested.Nested1 MyManagedStruct.Nested.n""
IL_0028: ldc.i4 0x1c8
IL_002d: call ""void MyManagedStruct.Nested.Nested1.mutate(int)""
IL_0032: ldfld ""MyManagedStruct cls1.y""
IL_0037: ldfld ""MyManagedStruct.Nested MyManagedStruct.n""
IL_003c: ldfld ""MyManagedStruct.Nested.Nested1 MyManagedStruct.Nested.n""
IL_0041: ldfld ""int MyManagedStruct.Nested.Nested1.num""
IL_0046: call ""void System.Console.WriteLine(int)""
IL_004b: ret
}
");
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册