提交 03d0ec83 编写于 作者: V vsadov

Retaining temporary IL slots allocated for passing rvalues as lvalues for the...

Retaining temporary IL slots allocated for passing rvalues as lvalues for the duration of the whole encompassing expression.

Passing rvalues to "in" parameters require allocation of temporary IL slots.

```C#
var result M1(42).ToString();

 // where  M is declared as
ref readonly int M1(in int x) {...}

// needs to be emitted as:
int temp = 42;
var result M1(ref temp) .ToString();   // passed by reference
```

This pattern bring an issue of the lifetime of such temporaries. Note that `M1` can return its argument back by reference, so the variable must exist as long as the return variable exists. - longer then the call to M1 itself.

The situation where we would have to pass an rvalue by reference via a copy was possible before, but it was very rare, so the solution was to just "leak" the temp. I.E - do not release such temps back to the temp pool. This is not a good solution when the situation becomes more common.

In this change we introduce a mechanism that collects temps of this kind and keeps them as for the duration of the most encompassing expression.
We will use the same mechanism for the preexisting cases as well.

Why "encompassing expression" is a sufficient extent for the temps. Consider the following concerns -

1) It is not possible to store a result of a `ref readonly` call, so the temp does not need to be retained at block level. - we do not allow `ref readonly` locals in source.
2) Internally compiler can create long-lived `ref readonly` temps for the purpose of returning from exception regions. This is not causing a concern since rvalues are not returnable and can never be stored insuch refs.
3) We sometimes call struct methods on a temp, there is no concern with the life time of such temps since struct `this` it cannot be ref-returned.
4) Sometimes short-term ref temps become mapped to long-term temps as a result of async spilling. We do not need to handle that specially here since those already have appropriate life times when extracted into  block variables in lowering.
上级 03a3517b
......@@ -31,6 +31,11 @@ internal sealed partial class CodeGenerator
private readonly HashSet<LocalSymbol> _stackLocals;
// There are scenarios where rvalues need to be passed to ref/in parameters
// in such cases the values must be spilled into temps and retained for the entirety of
// the most encompasing expression.
private ArrayBuilder<LocalDefinition> _expressionTemps;
// not 0 when in a protected region with a handler.
private int _tryNestingLevel;
......@@ -267,6 +272,9 @@ private void GenerateImpl()
}
_synthesizedLocalOrdinals.Free();
Debug.Assert(!(_expressionTemps?.Count > 0), "leaking expression temps?");
_expressionTemps?.Free();
}
private void HandleReturn()
......@@ -407,5 +415,33 @@ private TextSpan EmitSequencePoint(SyntaxTree syntaxTree, TextSpan span)
_builder.DefineSequencePoint(syntaxTree, span);
return span;
}
private void AddExpressionTemp(LocalDefinition temp)
{
// in some cases like stack locals, there is no slot allocated.
if (temp == null)
{
return;
}
var exprTemps = _expressionTemps ?? (_expressionTemps = ArrayBuilder<LocalDefinition>.GetInstance());
Debug.Assert(!exprTemps.Contains(temp));
exprTemps.Add(temp);
}
private void ReleaseExpressionTemps()
{
if (_expressionTemps?.Count > 0)
{
// release in reverse order to keep same temps on top of the temp stack if possible
for(int i = _expressionTemps.Count - 1; i >= 0; i--)
{
var temp = _expressionTemps[i];
FreeTemp(temp);
}
_expressionTemps.Clear();
}
}
}
}
......@@ -272,42 +272,12 @@ private LocalDefinition EmitAddressOfTempClone(BoundExpression expression)
/// </summary>
private LocalDefinition EmitSequenceAddress(BoundSequence sequence, AddressKind addressKind)
{
var hasLocals = !sequence.Locals.IsEmpty;
if (hasLocals)
{
_builder.OpenLocalScope();
foreach (var local in sequence.Locals)
{
DefineLocal(local, sequence.Syntax);
}
}
DefineAndRecordLocals(sequence);
EmitSideEffects(sequence);
var tempOpt = EmitAddress(sequence.Value, addressKind);
// when a sequence is happened to be a byref receiver
// we may need to extend the life time of the target until we are done accessing it
// {.v ; v = Foo(); v}.Bar() // v should be released only after Bar() is done.
LocalSymbol doNotRelease = null;
if (tempOpt == null)
{
doNotRelease = DigForValueLocal(sequence);
if (doNotRelease != null)
{
tempOpt = GetLocal(doNotRelease);
}
}
var result = EmitAddress(sequence.Value, addressKind);
CloseScopeAndKeepLocals(sequence);
FreeLocals(sequence, doNotRelease);
return tempOpt;
}
// if sequence value is a local scoped to the sequence, return that local
private LocalSymbol DigForValueLocal(BoundSequence topSequence)
{
return DigForValueLocal(topSequence, topSequence.Value);
return result;
}
private LocalSymbol DigForValueLocal(BoundSequence topSequence, BoundExpression value)
......@@ -431,7 +401,7 @@ private bool HasHome(BoundFieldAccess fieldAccess, bool needWriteable)
if (!field.IsReadOnly)
{
//PROTOTYPE(readonlyRefs): should we dig through struct receivers?
//PROTOTYPE(verifier): 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;
......
......@@ -6,6 +6,7 @@
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeGen
......@@ -423,7 +424,7 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces
}
else
{
//PROTOTYPE(readonlyRefs): this does not need to be writeable
//PROTOTYPE(verifier): 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);
......@@ -432,9 +433,11 @@ 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);
// this does not need to be writeable.
// we may call "HasValue" on this, but it is not mutating
// besides, since we are not making a copy, the receiver is not a field,
// so it cannot be readonly, in verifier sense, anyways.
receiverTemp = EmitReceiverRef(receiver, AddressKind.ReadOnly);
// here we have loaded just { O } or {&nub}
// we have the most trivial case where we can just reload receiver when needed again
}
......@@ -585,15 +588,18 @@ private void EmitArgListOperator(BoundArgListOperator expression)
private void EmitArgument(BoundExpression argument, RefKind refKind)
{
switch(refKind)
switch (refKind)
{
case RefKind.None:
EmitExpression(argument, true);
break;
case RefKind.RefReadOnly:
//PROTOTYPE(reaadonlyRefs): leaking a temp here
var temp = EmitAddress(argument, AddressKind.ReadOnly);
if (temp != null)
{
AddExpressionTemp(temp);
}
break;
default:
......@@ -717,7 +723,7 @@ private void EmitSequenceExpression(BoundSequence sequence, bool used)
}
// sequence is used as a value, can release all locals
FreeLocals(sequence, doNotRelease: null);
FreeLocals(sequence);
}
private void DefineLocals(BoundSequence sequence)
......@@ -735,7 +741,7 @@ private void DefineLocals(BoundSequence sequence)
}
}
private void FreeLocals(BoundSequence sequence, LocalSymbol doNotRelease)
private void FreeLocals(BoundSequence sequence)
{
if (sequence.Locals.IsEmpty)
{
......@@ -746,11 +752,43 @@ private void FreeLocals(BoundSequence sequence, LocalSymbol doNotRelease)
foreach (var local in sequence.Locals)
{
if ((object)local != doNotRelease)
{
FreeLocal(local);
}
FreeLocal(local);
}
}
/// <summary>
/// Defines sequence locals and record them so tht they could be retained for the duration of the encompassing expresson
/// Use this when taking a reference of the sequence, which can indirectly refer toany of its locals.
/// </summary>
private void DefineAndRecordLocals(BoundSequence sequence)
{
if (sequence.Locals.IsEmpty)
{
return;
}
_builder.OpenLocalScope();
foreach (var local in sequence.Locals)
{
var seqLocal = DefineLocal(local, sequence.Syntax);
AddExpressionTemp(seqLocal);
}
}
/// <summary>
/// Closes the visibility/debug scopes for the sequence locals, but keep the local slots from reuse
/// for the duration of the encompassing expresson.
/// Use this paired with DefineAndRecordLocals when taking a reference of the sequence, which can indirectly refer to any of its locals.
/// </summary>
private void CloseScopeAndKeepLocals(BoundSequence sequence)
{
if (sequence.Locals.IsEmpty)
{
return;
}
_builder.CloseLocalScope();
}
private void EmitSideEffects(BoundSequence sequence)
......@@ -2228,14 +2266,12 @@ private bool EmitAssignmentPreamble(BoundAssignmentOperator assignmentOperator)
{
var sequence = (BoundSequence)assignmentTarget;
DefineLocals(sequence);
// NOTE: not releasing sequence locals right away.
// Since sequence is used as a variable, we will keep the locals for the extent of the containing expression
DefineAndRecordLocals(sequence);
EmitSideEffects(sequence);
lhsUsesStack = EmitAssignmentPreamble(assignmentOperator.Update(sequence.Value, assignmentOperator.Right, assignmentOperator.RefKind, assignmentOperator.Type));
// doNotRelease will be released in EmitStore after we are done with the whole assignment.
var doNotRelease = DigForValueLocal(sequence);
FreeLocals(sequence, doNotRelease);
CloseScopeAndKeepLocals(sequence);
}
break;
......@@ -2281,14 +2317,35 @@ private void EmitAssignmentValue(BoundAssignmentOperator assignmentOperator)
}
else
{
// LEAKING A TEMP IS OK HERE
// 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 copy in synthetic code and we have to leak those.
// fortunately these are very short-lived temps that should not cause value sharing.
var exprTempsBefore = _expressionTemps?.Count ?? 0;
var temp = EmitAddress(assignmentOperator.Right, AddressKind.Writeable);
#if DEBUG
Debug.Assert(temp == null || ((SynthesizedLocal)assignmentOperator.Left.ExpressionSymbol).SynthesizedKind == SynthesizedLocalKind.LoweringTemp);
#endif
// 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.
// Retain those temps for the extent of the encompassing expression.
AddExpressionTemp(temp);
// are we, by the way, ref-assigning to something that lives longer than encompassing expression?
if (((BoundLocal)assignmentOperator.Left).LocalSymbol.SynthesizedKind.IsLongLived())
{
var exprTempsAfter = _expressionTemps?.Count ?? 0;
Debug.Assert(exprTempsBefore == exprTempsAfter,
"Expr temps produced when ref-assinging to a long-lived variable. Life time of those temps may be insufficient.");
// I cannot see how this could happen, thus there is an assert above.
// If that does happen, we would need to understand whether that is intentional.
//
// However, if it were to happen, leaking those extra temps would be the right thing to do.
// Any of them could be directly or indirectly referred by the LHS now
// and we do not know the scope of the LHS - could be the whole method.
//
if (exprTempsAfter > exprTempsAfter)
{
_expressionTemps.Count = exprTempsBefore;
}
}
}
}
......@@ -2404,12 +2461,6 @@ private void EmitStore(BoundAssignmentOperator assignment)
{
var sequence = (BoundSequence)expression;
EmitStore(assignment.Update(sequence.Value, assignment.Right, assignment.RefKind, assignment.Type));
var notReleased = DigForValueLocal(sequence);
if (notReleased != null)
{
FreeLocal(notReleased);
}
}
break;
......
......@@ -92,6 +92,8 @@ private void EmitStatement(BoundStatement statement)
_builder.AssertStackEmpty();
}
#endif
ReleaseExpressionTemps();
}
private int EmitStatementAndCountInstructions(BoundStatement statement)
......@@ -565,7 +567,7 @@ private void EmitSequenceCondBranch(BoundSequence sequence, ref object dest, boo
EmitCondBranch(sequence.Value, ref dest, sense);
// sequence is used as a value, can release all locals
FreeLocals(sequence, doNotRelease: null);
FreeLocals(sequence);
}
private void EmitLabelStatement(BoundLabelStatement boundLabelStatement)
......@@ -611,6 +613,9 @@ private void EmitBlock(BoundBlock block)
foreach (var local in block.Locals)
{
Debug.Assert(local.RefKind == RefKind.None || local.SynthesizedKind.IsLongLived(),
"A ref local ended up in a block and claims it is shortlived. That is dangerous. Are we sure it is short lived?");
var declaringReferences = local.DeclaringSyntaxReferences;
DefineLocal(local, !declaringReferences.IsEmpty ? (CSharpSyntaxNode)declaringReferences[0].GetSyntax() : block.Syntax);
}
......@@ -1227,7 +1232,8 @@ private void EmitConstantSwitchHeader(BoundExpression expression, LabelSymbol ta
if (sequence != null)
{
FreeLocals(sequence, doNotRelease: null);
// sequence was used as a value, can release all its locals.
FreeLocals(sequence);
}
}
......
......@@ -144,19 +144,6 @@ public void AddLocal(LocalSymbol local, DiagnosticBag diagnostics)
_locals.Add(local);
}
internal void AddLocals(ImmutableArray<LocalSymbol> locals)
{
if (_locals == null)
{
_locals = ArrayBuilder<LocalSymbol>.GetInstance();
}
foreach (var local in locals)
{
_locals.Add(local);
}
}
public void AddStatement(BoundStatement statement)
{
if (_statements == null)
......@@ -348,14 +335,14 @@ private BoundStatement UpdateStatement(BoundSpillSequenceBuilder builder, BoundS
continue;
case BoundKind.Sequence:
// We don't need promote short-lived variables defined by the sequence to long-lived,
// since neither the side-effects nor the value of the sequence contains await
// neither the side-effects nor the value of the sequence contains await
// (otherwise it would be converted to a SpillSequenceBuilder).
var sequence = (BoundSequence)expression;
builder.AddLocals(sequence.Locals);
builder.AddExpressions(sequence.SideEffects);
expression = sequence.Value;
continue;
if (refKind != RefKind.None)
{
return expression;
}
goto default;
case BoundKind.ThisReference:
case BoundKind.BaseReference:
......
......@@ -455,7 +455,7 @@ private static bool IsSafeForReordering(BoundExpression expression, RefKind kind
// Step one: Store everything that is non-trivial into a temporary; record the
// stores in storesToTemps and make the actual argument a reference to the temp.
// Do not yet attempt to deal with params arrays or optional arguments.
BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKindsOpt, rewrittenArguments, actualArguments, refKinds, storesToTemps);
BuildStoresToTemps(expanded, argsToParamsOpt, parameters, rewrittenArguments, actualArguments, refKinds, storesToTemps);
// all the formal arguments, except missing optionals, are now in place.
......@@ -602,7 +602,7 @@ private static ImmutableArray<RefKind> GetRefKindsOrNull(ArrayBuilder<RefKind> r
private void BuildStoresToTemps(
bool expanded,
ImmutableArray<int> argsToParamsOpt,
ImmutableArray<RefKind> argumentRefKinds,
ImmutableArray<ParameterSymbol> parameters,
ImmutableArray<BoundExpression> rewrittenArguments,
/* out */ BoundExpression[] arguments,
/* out */ ArrayBuilder<RefKind> refKinds,
......@@ -615,7 +615,7 @@ private static ImmutableArray<RefKind> GetRefKindsOrNull(ArrayBuilder<RefKind> r
{
BoundExpression argument = rewrittenArguments[a];
int p = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[a] : a;
RefKind refKind = argumentRefKinds.RefKinds(a);
RefKind refKind = parameters[p].RefKind;
Debug.Assert(arguments[p] == null);
// Unfortunately, we violate the specification and allow:
......
......@@ -280,7 +280,7 @@ private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAcce
// Step one: Store everything that is non-trivial into a temporary; record the
// stores in storesToTemps and make the actual argument a reference to the temp.
// Do not yet attempt to deal with params arrays or optional arguments.
BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps);
BuildStoresToTemps(expanded, argsToParamsOpt, parameters, rewrittenArguments, actualArguments, refKinds, storesToTemps);
// Step two: If we have a params array, build the array and fill in the argument.
if (expanded)
......
......@@ -1275,7 +1275,7 @@ internal static BoundExpression NullOrDefault(TypeSymbol typeSymbol, SyntaxNode
#endif
)
{
if (refKind == RefKind.Out)
if (refKind == RefKind.Out || refKind == RefKind.RefReadOnly)
{
refKind = RefKind.Ref;
}
......
......@@ -599,15 +599,14 @@ public static async Task<int> F(int[] array)
v.VerifyIL("Test.<F>d__2.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @"
{
// Code size 290 (0x122)
// Code size 285 (0x11d)
.maxstack 5
.locals init (int V_0,
int V_1,
int& V_2,
int V_3,
System.Runtime.CompilerServices.TaskAwaiter<int> V_4,
Test.<F>d__2 V_5,
System.Exception V_6)
int V_2,
System.Runtime.CompilerServices.TaskAwaiter<int> V_3,
Test.<F>d__2 V_4,
System.Exception V_5)
~IL_0000: ldarg.0
IL_0001: ldfld ""int Test.<F>d__2.<>1__state""
IL_0006: stloc.0
......@@ -616,130 +615,285 @@ .maxstack 5
~IL_0007: ldloc.0
IL_0008: brfalse.s IL_000c
IL_000a: br.s IL_000e
IL_000c: br.s IL_008c
IL_000c: br.s IL_0088
-IL_000e: nop
-IL_000f: ldarg.0
IL_0010: ldfld ""int[] Test.<F>d__2.array""
IL_0015: ldc.i4.1
IL_0016: ldelema ""int""
IL_001b: stloc.2
IL_001c: ldarg.0
IL_001d: ldloc.2
IL_001e: ldloc.2
IL_001f: ldind.i4
IL_0020: ldc.i4.2
IL_0021: add
IL_0022: dup
IL_0023: stloc.3
IL_0024: stind.i4
IL_0025: ldloc.3
IL_0026: stfld ""int Test.<F>d__2.<>s__1""
IL_002b: ldarg.0
IL_002c: ldarg.0
IL_002d: ldfld ""int[] Test.<F>d__2.array""
IL_0032: stfld ""int[] Test.<F>d__2.<>s__5""
IL_0037: ldarg.0
IL_0038: ldfld ""int[] Test.<F>d__2.<>s__5""
IL_003d: ldc.i4.3
IL_003e: ldelem.i4
IL_003f: pop
IL_0040: ldarg.0
IL_0041: ldarg.0
IL_0042: ldfld ""int[] Test.<F>d__2.<>s__5""
IL_0047: ldc.i4.3
IL_0048: ldelem.i4
IL_0049: stfld ""int Test.<F>d__2.<>s__2""
IL_004e: call ""System.Threading.Tasks.Task<int> Test.G()""
IL_0053: callvirt ""System.Runtime.CompilerServices.TaskAwaiter<int> System.Threading.Tasks.Task<int>.GetAwaiter()""
IL_0058: stloc.s V_4
~IL_005a: ldloca.s V_4
IL_005c: call ""bool System.Runtime.CompilerServices.TaskAwaiter<int>.IsCompleted.get""
IL_0061: brtrue.s IL_00a9
IL_0063: ldarg.0
IL_0064: ldc.i4.0
IL_0065: dup
IL_0066: stloc.0
IL_0067: stfld ""int Test.<F>d__2.<>1__state""
<IL_006c: ldarg.0
IL_006d: ldloc.s V_4
IL_006f: stfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_0074: ldarg.0
IL_0075: stloc.s V_5
IL_0010: ldarg.0
IL_0011: ldfld ""int[] Test.<F>d__2.array""
IL_0016: ldc.i4.1
IL_0017: ldelema ""int""
IL_001c: dup
IL_001d: ldind.i4
IL_001e: ldc.i4.2
IL_001f: add
IL_0020: dup
IL_0021: stloc.2
IL_0022: stind.i4
IL_0023: ldloc.2
IL_0024: stfld ""int Test.<F>d__2.<>s__1""
IL_0029: ldarg.0
IL_002a: ldarg.0
IL_002b: ldfld ""int[] Test.<F>d__2.array""
IL_0030: stfld ""int[] Test.<F>d__2.<>s__5""
IL_0035: ldarg.0
IL_0036: ldfld ""int[] Test.<F>d__2.<>s__5""
IL_003b: ldc.i4.3
IL_003c: ldelem.i4
IL_003d: pop
IL_003e: ldarg.0
IL_003f: ldarg.0
IL_0040: ldfld ""int[] Test.<F>d__2.<>s__5""
IL_0045: ldc.i4.3
IL_0046: ldelem.i4
IL_0047: stfld ""int Test.<F>d__2.<>s__2""
IL_004c: call ""System.Threading.Tasks.Task<int> Test.G()""
IL_0051: callvirt ""System.Runtime.CompilerServices.TaskAwaiter<int> System.Threading.Tasks.Task<int>.GetAwaiter()""
IL_0056: stloc.3
~IL_0057: ldloca.s V_3
IL_0059: call ""bool System.Runtime.CompilerServices.TaskAwaiter<int>.IsCompleted.get""
IL_005e: brtrue.s IL_00a4
IL_0060: ldarg.0
IL_0061: ldc.i4.0
IL_0062: dup
IL_0063: stloc.0
IL_0064: stfld ""int Test.<F>d__2.<>1__state""
<IL_0069: ldarg.0
IL_006a: ldloc.3
IL_006b: stfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_0070: ldarg.0
IL_0071: stloc.s V_4
IL_0073: ldarg.0
IL_0074: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_0079: ldloca.s V_3
IL_007b: ldloca.s V_4
IL_007d: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.AwaitUnsafeOnCompleted<System.Runtime.CompilerServices.TaskAwaiter<int>, Test.<F>d__2>(ref System.Runtime.CompilerServices.TaskAwaiter<int>, ref Test.<F>d__2)""
IL_0082: nop
IL_0083: leave IL_011c
>IL_0088: ldarg.0
IL_0089: ldfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_008e: stloc.3
IL_008f: ldarg.0
IL_0090: ldflda ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_0095: initobj ""System.Runtime.CompilerServices.TaskAwaiter<int>""
IL_009b: ldarg.0
IL_009c: ldc.i4.m1
IL_009d: dup
IL_009e: stloc.0
IL_009f: stfld ""int Test.<F>d__2.<>1__state""
IL_00a4: ldarg.0
IL_00a5: ldloca.s V_3
IL_00a7: call ""int System.Runtime.CompilerServices.TaskAwaiter<int>.GetResult()""
IL_00ac: stfld ""int Test.<F>d__2.<>s__3""
IL_00b1: ldarg.0
IL_00b2: ldarg.0
IL_00b3: ldfld ""int[] Test.<F>d__2.<>s__5""
IL_00b8: ldc.i4.3
IL_00b9: ldarg.0
IL_00ba: ldfld ""int Test.<F>d__2.<>s__2""
IL_00bf: ldarg.0
IL_00c0: ldfld ""int Test.<F>d__2.<>s__3""
IL_00c5: add
IL_00c6: dup
IL_00c7: stloc.2
IL_00c8: stelem.i4
IL_00c9: ldloc.2
IL_00ca: stfld ""int Test.<F>d__2.<>s__4""
IL_00cf: ldarg.0
IL_00d0: ldfld ""int Test.<F>d__2.<>s__1""
IL_00d5: ldarg.0
IL_00d6: ldfld ""int Test.<F>d__2.<>s__4""
IL_00db: ldc.i4.4
IL_00dc: call ""int Test.H(int, int, int)""
IL_00e1: pop
IL_00e2: ldarg.0
IL_00e3: ldnull
IL_00e4: stfld ""int[] Test.<F>d__2.<>s__5""
-IL_00e9: ldc.i4.1
IL_00ea: stloc.1
IL_00eb: leave.s IL_0107
}
catch System.Exception
{
~IL_00ed: stloc.s V_5
IL_00ef: ldarg.0
IL_00f0: ldc.i4.s -2
IL_00f2: stfld ""int Test.<F>d__2.<>1__state""
IL_00f7: ldarg.0
IL_00f8: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_00fd: ldloc.s V_5
IL_00ff: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.SetException(System.Exception)""
IL_0104: nop
IL_0105: leave.s IL_011c
}
-IL_0107: ldarg.0
IL_0108: ldc.i4.s -2
IL_010a: stfld ""int Test.<F>d__2.<>1__state""
~IL_010f: ldarg.0
IL_0110: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_0115: ldloc.1
IL_0116: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.SetResult(int)""
IL_011b: nop
IL_011c: ret
}", sequencePoints: "Test+<F>d__2.MoveNext");
}
[Fact]
public void SpillSequencesRelease()
{
var source = @"
using System.Threading.Tasks;
public class Test
{
public static int H(int a, int b, int c)
{
return a;
}
public static Task<int> G()
{
return null;
}
public static async Task<int> F(int[] array)
{
H(array[1] += 2, array[3] += await G(), 4);
return 1;
}
}
";
var v = CompileAndVerify(source, options: TestOptions.ReleaseDll);
v.VerifyIL("Test.<F>d__2.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @"
{
// Code size 260 (0x104)
.maxstack 4
.locals init (int V_0,
int V_1,
int V_2,
int V_3,
int V_4,
System.Runtime.CompilerServices.TaskAwaiter<int> V_5,
System.Exception V_6)
~IL_0000: ldarg.0
IL_0001: ldfld ""int Test.<F>d__2.<>1__state""
IL_0006: stloc.0
.try
{
~IL_0007: ldloc.0
IL_0008: brfalse.s IL_0082
-IL_000a: ldarg.0
IL_000b: ldarg.0
IL_000c: ldfld ""int[] Test.<F>d__2.array""
IL_0011: ldc.i4.1
IL_0012: ldelema ""int""
IL_0017: dup
IL_0018: ldind.i4
IL_0019: ldc.i4.2
IL_001a: add
IL_001b: dup
IL_001c: stloc.s V_4
IL_001e: stind.i4
IL_001f: ldloc.s V_4
IL_0021: stfld ""int Test.<F>d__2.<>7__wrap1""
IL_0026: ldarg.0
IL_0027: ldarg.0
IL_0028: ldfld ""int[] Test.<F>d__2.array""
IL_002d: stfld ""int[] Test.<F>d__2.<>7__wrap3""
IL_0032: ldarg.0
IL_0033: ldfld ""int[] Test.<F>d__2.<>7__wrap3""
IL_0038: ldc.i4.3
IL_0039: ldelem.i4
IL_003a: pop
IL_003b: ldarg.0
IL_003c: ldarg.0
IL_003d: ldfld ""int[] Test.<F>d__2.<>7__wrap3""
IL_0042: ldc.i4.3
IL_0043: ldelem.i4
IL_0044: stfld ""int Test.<F>d__2.<>7__wrap2""
IL_0049: call ""System.Threading.Tasks.Task<int> Test.G()""
IL_004e: callvirt ""System.Runtime.CompilerServices.TaskAwaiter<int> System.Threading.Tasks.Task<int>.GetAwaiter()""
IL_0053: stloc.s V_5
~IL_0055: ldloca.s V_5
IL_0057: call ""bool System.Runtime.CompilerServices.TaskAwaiter<int>.IsCompleted.get""
IL_005c: brtrue.s IL_009f
IL_005e: ldarg.0
IL_005f: ldc.i4.0
IL_0060: dup
IL_0061: stloc.0
IL_0062: stfld ""int Test.<F>d__2.<>1__state""
<IL_0067: ldarg.0
IL_0068: ldloc.s V_5
IL_006a: stfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_006f: ldarg.0
IL_0070: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_0075: ldloca.s V_5
IL_0077: ldarg.0
IL_0078: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_007d: ldloca.s V_4
IL_007f: ldloca.s V_5
IL_0081: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.AwaitUnsafeOnCompleted<System.Runtime.CompilerServices.TaskAwaiter<int>, Test.<F>d__2>(ref System.Runtime.CompilerServices.TaskAwaiter<int>, ref Test.<F>d__2)""
IL_0086: nop
IL_0087: leave IL_0121
>IL_008c: ldarg.0
IL_008d: ldfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_0092: stloc.s V_4
IL_0094: ldarg.0
IL_0095: ldflda ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_009a: initobj ""System.Runtime.CompilerServices.TaskAwaiter<int>""
IL_00a0: ldarg.0
IL_00a1: ldc.i4.m1
IL_00a2: dup
IL_00a3: stloc.0
IL_00a4: stfld ""int Test.<F>d__2.<>1__state""
IL_00a9: ldarg.0
IL_00aa: ldloca.s V_4
IL_00ac: call ""int System.Runtime.CompilerServices.TaskAwaiter<int>.GetResult()""
IL_00b1: stfld ""int Test.<F>d__2.<>s__3""
IL_00b6: ldarg.0
IL_00b7: ldarg.0
IL_00b8: ldfld ""int[] Test.<F>d__2.<>s__5""
IL_00bd: ldc.i4.3
IL_00be: ldarg.0
IL_00bf: ldfld ""int Test.<F>d__2.<>s__2""
IL_00c4: ldarg.0
IL_00c5: ldfld ""int Test.<F>d__2.<>s__3""
IL_00ca: add
IL_00cb: dup
IL_00cc: stloc.3
IL_00cd: stelem.i4
IL_00ce: ldloc.3
IL_00cf: stfld ""int Test.<F>d__2.<>s__4""
IL_00d4: ldarg.0
IL_00d5: ldfld ""int Test.<F>d__2.<>s__1""
IL_00da: ldarg.0
IL_00db: ldfld ""int Test.<F>d__2.<>s__4""
IL_00e0: ldc.i4.4
IL_00e1: call ""int Test.H(int, int, int)""
IL_00e6: pop
IL_00e7: ldarg.0
IL_00e8: ldnull
IL_00e9: stfld ""int[] Test.<F>d__2.<>s__5""
-IL_00ee: ldc.i4.1
IL_00ef: stloc.1
IL_00f0: leave.s IL_010c
IL_0078: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.AwaitUnsafeOnCompleted<System.Runtime.CompilerServices.TaskAwaiter<int>, Test.<F>d__2>(ref System.Runtime.CompilerServices.TaskAwaiter<int>, ref Test.<F>d__2)""
IL_007d: leave IL_0103
>IL_0082: ldarg.0
IL_0083: ldfld ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_0088: stloc.s V_5
IL_008a: ldarg.0
IL_008b: ldflda ""System.Runtime.CompilerServices.TaskAwaiter<int> Test.<F>d__2.<>u__1""
IL_0090: initobj ""System.Runtime.CompilerServices.TaskAwaiter<int>""
IL_0096: ldarg.0
IL_0097: ldc.i4.m1
IL_0098: dup
IL_0099: stloc.0
IL_009a: stfld ""int Test.<F>d__2.<>1__state""
IL_009f: ldloca.s V_5
IL_00a1: call ""int System.Runtime.CompilerServices.TaskAwaiter<int>.GetResult()""
IL_00a6: stloc.2
IL_00a7: ldarg.0
IL_00a8: ldfld ""int[] Test.<F>d__2.<>7__wrap3""
IL_00ad: ldc.i4.3
IL_00ae: ldarg.0
IL_00af: ldfld ""int Test.<F>d__2.<>7__wrap2""
IL_00b4: ldloc.2
IL_00b5: add
IL_00b6: dup
IL_00b7: stloc.s V_4
IL_00b9: stelem.i4
IL_00ba: ldloc.s V_4
IL_00bc: stloc.3
IL_00bd: ldarg.0
IL_00be: ldfld ""int Test.<F>d__2.<>7__wrap1""
IL_00c3: ldloc.3
IL_00c4: ldc.i4.4
IL_00c5: call ""int Test.H(int, int, int)""
IL_00ca: pop
IL_00cb: ldarg.0
IL_00cc: ldnull
IL_00cd: stfld ""int[] Test.<F>d__2.<>7__wrap3""
-IL_00d2: ldc.i4.1
IL_00d3: stloc.1
IL_00d4: leave.s IL_00ef
}
catch System.Exception
{
~IL_00f2: stloc.s V_6
IL_00f4: ldarg.0
IL_00f5: ldc.i4.s -2
IL_00f7: stfld ""int Test.<F>d__2.<>1__state""
IL_00fc: ldarg.0
IL_00fd: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_0102: ldloc.s V_6
IL_0104: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.SetException(System.Exception)""
IL_0109: nop
IL_010a: leave.s IL_0121
~IL_00d6: stloc.s V_6
IL_00d8: ldarg.0
IL_00d9: ldc.i4.s -2
IL_00db: stfld ""int Test.<F>d__2.<>1__state""
IL_00e0: ldarg.0
IL_00e1: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_00e6: ldloc.s V_6
IL_00e8: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.SetException(System.Exception)""
IL_00ed: leave.s IL_0103
}
-IL_010c: ldarg.0
IL_010d: ldc.i4.s -2
IL_010f: stfld ""int Test.<F>d__2.<>1__state""
~IL_0114: ldarg.0
IL_0115: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_011a: ldloc.1
IL_011b: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.SetResult(int)""
IL_0120: nop
IL_0121: ret
-IL_00ef: ldarg.0
IL_00f0: ldc.i4.s -2
IL_00f2: stfld ""int Test.<F>d__2.<>1__state""
~IL_00f7: ldarg.0
IL_00f8: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> Test.<F>d__2.<>t__builder""
IL_00fd: ldloc.1
IL_00fe: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.SetResult(int)""
IL_0103: ret
}", sequencePoints: "Test+<F>d__2.MoveNext");
}
[Fact]
public void SpillSequencesInConditionalExpression1()
{
......
......@@ -117,6 +117,8 @@ public static void Main()
{
System.Console.WriteLine(M(42));
System.Console.WriteLine(new Program()[5, 6]);
System.Console.WriteLine(M(42));
System.Console.WriteLine(M(42));
}
static ref readonly int M(in int x)
......@@ -129,15 +131,16 @@ public static void Main()
";
var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular, verify: false, expectedOutput: @"42
11");
11
42
42");
comp.VerifyIL("Program.Main()", @"
{
// Code size 40 (0x28)
// Code size 72 (0x48)
.maxstack 3
.locals init (int V_0,
int V_1,
int V_2)
int V_1)
IL_0000: ldc.i4.s 42
IL_0002: stloc.0
IL_0003: ldloca.s V_0
......@@ -146,14 +149,26 @@ .maxstack 3
IL_000b: call ""void System.Console.WriteLine(int)""
IL_0010: newobj ""Program..ctor()""
IL_0015: ldc.i4.5
IL_0016: stloc.1
IL_0017: ldloca.s V_1
IL_0016: stloc.0
IL_0017: ldloca.s V_0
IL_0019: ldc.i4.6
IL_001a: stloc.2
IL_001b: ldloca.s V_2
IL_001a: stloc.1
IL_001b: ldloca.s V_1
IL_001d: call ""int Program.this[in int, in int].get""
IL_0022: call ""void System.Console.WriteLine(int)""
IL_0027: ret
IL_0027: ldc.i4.s 42
IL_0029: stloc.0
IL_002a: ldloca.s V_0
IL_002c: call ""ref readonly int Program.M(in int)""
IL_0031: ldind.i4
IL_0032: call ""void System.Console.WriteLine(int)""
IL_0037: ldc.i4.s 42
IL_0039: stloc.0
IL_003a: ldloca.s V_0
IL_003c: call ""ref readonly int Program.M(in int)""
IL_0041: ldind.i4
IL_0042: call ""void System.Console.WriteLine(int)""
IL_0047: ret
}");
}
......@@ -681,5 +696,154 @@ .locals init (double V_0)
IL_0010: ret
}");
}
[Fact]
public void ReadonlyParamAsyncSpill1()
{
var text = @"
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Test().Wait();
}
public static async Task Test()
{
M1(1, await GetT(2), 3);
}
public static async Task<T> GetT<T>(T val)
{
await Task.Yield();
return val;
}
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.ReleaseExe);
CompileAndVerify(comp, verify: false, expectedOutput: @"6");
}
[Fact]
public void ReadonlyParamAsyncSpill2()
{
var text = @"
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Test().Wait();
}
public static async Task Test()
{
M1(await GetT(1), await GetT(2), 3);
}
public static async Task<T> GetT<T>(T val)
{
await Task.Yield();
return val;
}
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.ReleaseExe);
CompileAndVerify(comp, verify: false, expectedOutput: @"6");
}
[WorkItem(20764, "https://github.com/dotnet/roslyn/issues/20764")]
[Fact]
public void ReadonlyParamAsyncSpill3()
{
var text = @"
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Test().Wait();
}
public static async Task Test()
{
var local = new S1();
// prints 3 42 3 3 note the aliasing, 3 is the last state of the local.f
M1(GetLocal(ref local).f, 42, GetLocal(ref local).f, GetLocal(ref local).f);
local = new S1();
// prints 1 42 3 3 note no aliasing for the first argument because of spilling
M1(GetLocal(ref local).f, await GetT(42), GetLocal(ref local).f, GetLocal(ref local).f);
local = new S1();
// prints 0 42 2 2 note no aliasing for the first argument because of spilling
// NOTE!!! in this case we _could_ actually arrange aliasing and we would, if it was an ordinary 'ref'
M1(local.f, await GetT(42), GetLocal(ref local).f, GetLocal(ref local).f);
}
private static ref readonly S1 GetLocal(ref S1 local)
{
local.f++;
return ref local;
}
public static async Task<T> GetT<T>(T val)
{
await Task.Yield();
return val;
}
public static void M1(in int arg1, in int arg2, in int arg3, in int arg4)
{
System.Console.WriteLine(arg1);
System.Console.WriteLine(arg2);
System.Console.WriteLine(arg3);
System.Console.WriteLine(arg4);
}
}
public struct S1
{
public int f;
}
";
var comp = CreateCompilationWithMscorlib46(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }, options: TestOptions.ReleaseExe);
CompileAndVerify(comp, verify: false, expectedOutput: @"
3
42
3
3
1
42
3
3
0
42
2
2");
}
}
}
......@@ -3921,8 +3921,7 @@ public static void Main()
// Code size 73 (0x49)
.maxstack 3
.locals init (int V_0,
T V_1,
T V_2)
T V_1)
IL_0000: ldarg.0
IL_0001: call ""T Test.Nop<T>(T)""
IL_0006: stloc.1
......@@ -3938,8 +3937,8 @@ .maxstack 3
IL_001f: callvirt ""void I.IntPropI.set""
IL_0024: ldarg.0
IL_0025: call ""T Test.Nop<T>(T)""
IL_002a: stloc.2
IL_002b: ldloca.s V_2
IL_002a: stloc.1
IL_002b: ldloca.s V_1
IL_002d: dup
IL_002e: constrained. ""T""
IL_0034: callvirt ""int I.IntPropI.get""
......
......@@ -7221,6 +7221,99 @@ .maxstack 2
IL_001c: br.s IL_001e
IL_001e: ldloc.1
IL_001f: ret
}");
}
[Fact]
public void ConditionalAccessOffReadOnlyNullable1()
{
var source = @"
using System;
class Program
{
private static readonly Guid? g = null;
static void Main()
{
Console.WriteLine(g?.ToString());
}
}
";
var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: @"");
verifier.VerifyIL("Program.Main", @"
{
// Code size 47 (0x2f)
.maxstack 2
.locals init (System.Guid? V_0,
System.Guid V_1)
IL_0000: nop
IL_0001: ldsfld ""System.Guid? Program.g""
IL_0006: stloc.0
IL_0007: ldloca.s V_0
IL_0009: dup
IL_000a: call ""bool System.Guid?.HasValue.get""
IL_000f: brtrue.s IL_0015
IL_0011: pop
IL_0012: ldnull
IL_0013: br.s IL_0028
IL_0015: call ""System.Guid System.Guid?.GetValueOrDefault()""
IL_001a: stloc.1
IL_001b: ldloca.s V_1
IL_001d: constrained. ""System.Guid""
IL_0023: callvirt ""string object.ToString()""
IL_0028: call ""void System.Console.WriteLine(string)""
IL_002d: nop
IL_002e: ret
}");
}
[Fact]
public void ConditionalAccessOffReadOnlyNullable2()
{
var source = @"
using System;
class Program
{
static void Main()
{
Console.WriteLine(default(Guid?)?.ToString());
}
}
";
var verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: @"");
verifier.VerifyIL("Program.Main", @"
{
// Code size 61 (0x3d)
.maxstack 1
.locals init (System.Guid? V_0,
System.Guid V_1)
IL_0000: nop
IL_0001: ldloca.s V_0
IL_0003: initobj ""System.Guid?""
IL_0009: ldloc.0
IL_000a: stloc.0
IL_000b: ldloca.s V_0
IL_000d: call ""bool System.Guid?.HasValue.get""
IL_0012: brtrue.s IL_0017
IL_0014: ldnull
IL_0015: br.s IL_0036
IL_0017: ldloca.s V_0
IL_0019: initobj ""System.Guid?""
IL_001f: ldloc.0
IL_0020: stloc.0
IL_0021: ldloca.s V_0
IL_0023: call ""System.Guid System.Guid?.GetValueOrDefault()""
IL_0028: stloc.1
IL_0029: ldloca.s V_1
IL_002b: constrained. ""System.Guid""
IL_0031: callvirt ""string object.ToString()""
IL_0036: call ""void System.Console.WriteLine(string)""
IL_003b: nop
IL_003c: ret
}");
}
}
......
......@@ -1922,8 +1922,9 @@ static void Main()
// Code size 116 (0x74)
.maxstack 4
.locals init (S V_0,
S V_1,
bool V_2)
S V_1,
bool V_2,
S V_3)
IL_0000: ldloca.s V_0
IL_0002: initobj ""S""
IL_0008: ldloca.s V_0
......@@ -1941,18 +1942,18 @@ .maxstack 4
IL_002e: callvirt ""bool object.Equals(object)""
IL_0033: stloc.2
IL_0034: ldloca.s V_2
IL_0036: ldloca.s V_0
IL_0036: ldloca.s V_1
IL_0038: initobj ""S""
IL_003e: ldloca.s V_0
IL_003e: ldloca.s V_1
IL_0040: ldc.i4.1
IL_0041: stfld ""int S.x""
IL_0046: ldloca.s V_0
IL_0048: ldloca.s V_1
IL_0046: ldloca.s V_1
IL_0048: ldloca.s V_3
IL_004a: initobj ""S""
IL_0050: ldloca.s V_1
IL_0050: ldloca.s V_3
IL_0052: ldc.i4.1
IL_0053: stfld ""int S.x""
IL_0058: ldloc.1
IL_0058: ldloc.3
IL_0059: box ""S""
IL_005e: constrained. ""S""
IL_0064: callvirt ""bool object.Equals(object)""
......@@ -1963,6 +1964,213 @@ .maxstack 4
");
}
[Fact]
public void InitTemp003()
{
string source = @"
using System;
readonly struct S
{
readonly int x;
public S(int x)
{
this.x = x;
}
static void Main()
{
// named argument reordering introduces a sequence with temps
// and we cannot know whether RefMethod returns a ref to a sequence local
// so we must assume that it can, and therefore must keep all the sequence the locals in use
// for the duration of the most-encompasing expression.
Console.WriteLine(RefMethod(arg2: I(5), arg1: I(3)).GreaterThen(
RefMethod(arg2: I(0), arg1: I(0))));
}
public static ref readonly S RefMethod(ref readonly S arg1, ref readonly S arg2)
{
return ref arg2;
}
public bool GreaterThen(ref readonly S arg)
{
return this.x > arg.x;
}
public static S I(int arg)
{
return new S(arg);
}
}
";
var compilation = CompileAndVerify(source, verify: false, expectedOutput: "True");
compilation.VerifyIL("S.Main",
@"
{
// Code size 63 (0x3f)
.maxstack 3
.locals init (S& V_0,
S V_1,
S V_2,
S& V_3,
S V_4,
S V_5)
IL_0000: ldc.i4.5
IL_0001: call ""S S.I(int)""
IL_0006: stloc.1
IL_0007: ldloca.s V_1
IL_0009: stloc.0
IL_000a: ldc.i4.3
IL_000b: call ""S S.I(int)""
IL_0010: stloc.2
IL_0011: ldloca.s V_2
IL_0013: ldloc.0
IL_0014: call ""ref readonly S S.RefMethod(in S, in S)""
IL_0019: ldc.i4.0
IL_001a: call ""S S.I(int)""
IL_001f: stloc.s V_4
IL_0021: ldloca.s V_4
IL_0023: stloc.3
IL_0024: ldc.i4.0
IL_0025: call ""S S.I(int)""
IL_002a: stloc.s V_5
IL_002c: ldloca.s V_5
IL_002e: ldloc.3
IL_002f: call ""ref readonly S S.RefMethod(in S, in S)""
IL_0034: call ""bool S.GreaterThen(in S)""
IL_0039: call ""void System.Console.WriteLine(bool)""
IL_003e: ret
}
");
}
[Fact]
public void InitTemp004()
{
string source = @"
using System;
readonly struct S
{
public readonly int x;
public S(int x)
{
this.x = x;
}
static void Main()
{
System.Console.Write(TestRO().x);
System.Console.WriteLine();
System.Console.Write(Test().x);
}
static ref readonly S TestRO()
{
try
{
// both args are refs
return ref RefMethodRO(arg2: I(5), arg1: I(3));
}
finally
{
// first arg is a value!!
RefMethodRO(arg2: I_Val(5), arg1: I(3));
}
}
public static ref readonly S RefMethodRO(ref readonly S arg1, ref readonly S arg2)
{
System.Console.Write(arg2.x);
return ref arg2;
}
// similar as above, but with regular (not readonly) refs for comparison
static ref S Test()
{
try
{
return ref RefMethod(arg2: ref I(5), arg1: ref I(3));
}
finally
{
var temp = I(5);
RefMethod(arg2: ref temp, arg1: ref I(3));
}
}
public static ref S RefMethod(ref S arg1, ref S arg2)
{
System.Console.Write(arg2.x);
return ref arg2;
}
private static S[] arr = new S[] { new S() };
public static ref S I(int arg)
{
arr[0] = new S(arg);
return ref arr[0];
}
public static S I_Val(int arg)
{
arr[0] = new S(arg);
return arr[0];
}
}
";
var compilation = CompileAndVerify(source, verify: false, expectedOutput: @"353
353");
compilation.VerifyIL("S.TestRO",
@"
{
// Code size 48 (0x30)
.maxstack 2
.locals init (S& V_0,
S& V_1,
S V_2)
.try
{
IL_0000: ldc.i4.5
IL_0001: call ""ref S S.I(int)""
IL_0006: stloc.0
IL_0007: ldc.i4.3
IL_0008: call ""ref S S.I(int)""
IL_000d: ldloc.0
IL_000e: call ""ref readonly S S.RefMethodRO(in S, in S)""
IL_0013: stloc.1
IL_0014: leave.s IL_002e
}
finally
{
IL_0016: ldc.i4.5
IL_0017: call ""S S.I_Val(int)""
IL_001c: stloc.2
IL_001d: ldloca.s V_2
IL_001f: stloc.0
IL_0020: ldc.i4.3
IL_0021: call ""ref S S.I(int)""
IL_0026: ldloc.0
IL_0027: call ""ref readonly S S.RefMethodRO(in S, in S)""
IL_002c: pop
IL_002d: endfinally
}
IL_002e: ldloc.1
IL_002f: ret
}
");
}
[WorkItem(842477, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/842477")]
[Fact]
public void DecimalConst()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册