提交 8b8bc93a 编写于 作者: N Neal Gafter

Optimize switch on a tuple literal

Fixes #20641
上级 662deba9
...@@ -1214,8 +1214,13 @@ private static bool SameTest(BoundDagTest x, BoundDagTest y) ...@@ -1214,8 +1214,13 @@ private static bool SameTest(BoundDagTest x, BoundDagTest y)
case BoundKind.DagValueTest: case BoundKind.DagValueTest:
return ((BoundDagValueTest)x).Value == ((BoundDagValueTest)y).Value; return ((BoundDagValueTest)x).Value == ((BoundDagValueTest)y).Value;
default: case BoundKind.DagNullTest:
case BoundKind.DagNonNullTest:
return true; return true;
default:
// For an evaluation, we defer to its .Equals
return x.Equals(y);
} }
} }
......
...@@ -17,7 +17,7 @@ private Symbol Symbol ...@@ -17,7 +17,7 @@ private Symbol Symbol
{ {
switch (this) switch (this)
{ {
case BoundDagFieldEvaluation e: return e.Field; case BoundDagFieldEvaluation e: return e.Field.CorrespondingTupleField ?? e.Field;
case BoundDagPropertyEvaluation e: return e.Property; case BoundDagPropertyEvaluation e: return e.Property;
case BoundDagTypeEvaluation e: return e.Type; case BoundDagTypeEvaluation e: return e.Type;
case BoundDagDeconstructEvaluation e: return e.DeconstructMethod; case BoundDagDeconstructEvaluation e: return e.DeconstructMethod;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Roslyn.Utilities; using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp namespace Microsoft.CodeAnalysis.CSharp
...@@ -11,6 +12,8 @@ partial class BoundDagTemp ...@@ -11,6 +12,8 @@ partial class BoundDagTemp
/// </summary> /// </summary>
public bool IsOriginalInput => this.Source == null; public bool IsOriginalInput => this.Source == null;
public static BoundDagTemp ForOriginalInput(SyntaxNode syntax, TypeSymbol type) => new BoundDagTemp(syntax, type, null, 0);
public override bool Equals(object obj) => obj is BoundDagTemp other && this.Equals(other); public override bool Equals(object obj) => obj is BoundDagTemp other && this.Equals(other);
public bool Equals(BoundDagTemp other) public bool Equals(BoundDagTemp other)
{ {
......
...@@ -116,7 +116,7 @@ public BoundDecisionDag Rewrite(Func<BoundDecisionDagNode, Func<BoundDecisionDag ...@@ -116,7 +116,7 @@ public BoundDecisionDag Rewrite(Func<BoundDecisionDagNode, Func<BoundDecisionDag
/// <param name="dag"></param> /// <param name="dag"></param>
/// <param name="replacement"></param> /// <param name="replacement"></param>
/// <returns></returns> /// <returns></returns>
public BoundDecisionDagNode TrivialReplacement(BoundDecisionDagNode dag, Func<BoundDecisionDagNode, BoundDecisionDagNode> replacement) public static BoundDecisionDagNode TrivialReplacement(BoundDecisionDagNode dag, Func<BoundDecisionDagNode, BoundDecisionDagNode> replacement)
{ {
switch (dag) switch (dag)
{ {
...@@ -192,5 +192,99 @@ BoundDecisionDagNode makeReplacement(BoundDecisionDagNode dag, Func<BoundDecisio ...@@ -192,5 +192,99 @@ BoundDecisionDagNode makeReplacement(BoundDecisionDagNode dag, Func<BoundDecisio
} }
} }
} }
#if DEBUG
/// <summary>
/// Starting with `this` state, produce a human-readable description of the state tables.
/// This is very useful for debugging and optimizing the dag state construction.
/// </summary>
internal new string Dump()
{
var allStates = this.TopologicallySortedNodes;
var stateIdentifierMap = PooledDictionary<BoundDecisionDagNode, int>.GetInstance();
for (int i = 0; i < allStates.Length; i++)
{
stateIdentifierMap.Add(allStates[i], i);
}
int nextTempNumber = 0;
var tempIdentifierMap = PooledDictionary<BoundDagEvaluation, int>.GetInstance();
int tempIdentifier(BoundDagEvaluation e)
{
return (e == null) ? 0 : tempIdentifierMap.TryGetValue(e, out int value) ? value : tempIdentifierMap[e] = ++nextTempNumber;
}
string tempName(BoundDagTemp t)
{
return $"t{tempIdentifier(t.Source)}{(t.Index != 0 ? $".{t.Index.ToString()}" : "")}";
}
var resultBuilder = PooledStringBuilder.GetInstance();
var result = resultBuilder.Builder;
foreach (var state in allStates)
{
result.AppendLine($"State " + stateIdentifierMap[state]);
switch (state)
{
case BoundTestDecisionDagNode node:
result.AppendLine($" Test: {dump(node.Test)}");
if (node.WhenTrue != null)
{
result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}");
}
if (node.WhenFalse != null)
{
result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}");
}
break;
case BoundEvaluationDecisionDagNode node:
result.AppendLine($" Test: {dump(node.Evaluation)}");
if (node.Next != null)
{
result.AppendLine($" Next: {stateIdentifierMap[node.Next]}");
}
break;
case BoundWhenDecisionDagNode node:
result.AppendLine($" WhenClause: " + node.WhenExpression.Syntax);
if (node.WhenTrue != null)
{
result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}");
}
if (node.WhenFalse != null)
{
result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}");
}
break;
case BoundLeafDecisionDagNode node:
result.AppendLine($" Case: " + node.Syntax);
break;
}
}
stateIdentifierMap.Free();
tempIdentifierMap.Free();
return resultBuilder.ToStringAndFree();
string dump(BoundDagTest d)
{
switch (d)
{
case BoundDagTypeEvaluation a:
return $"t{tempIdentifier(a)}={a.Kind}({a.Type.ToString()})";
case BoundDagEvaluation e:
return $"t{tempIdentifier(e)}={e.Kind}";
case BoundDagTypeTest b:
return $"?{d.Kind}({b.Type.ToString()}, {tempName(d.Input)})";
case BoundDagValueTest v:
return $"?{d.Kind}({v.Value.ToString()}, {tempName(d.Input)})";
default:
return $"?{d.Kind}({tempName(d.Input)})";
}
}
}
#endif
} }
} }
...@@ -145,6 +145,18 @@ public bool MightAssignSomething(BoundExpression expr) ...@@ -145,6 +145,18 @@ public bool MightAssignSomething(BoundExpression expr)
return this._mightAssignSomething; return this._mightAssignSomething;
} }
public override BoundNode VisitReturnStatement(BoundReturnStatement node)
{
if (node.RefKind != RefKind.None)
{
// might be taking a ref to a variable in a lambda that the caller assigns.
this._mightAssignSomething = true;
return null;
}
return base.VisitReturnStatement(node);
}
public override BoundNode VisitCall(BoundCall node) public override BoundNode VisitCall(BoundCall node)
{ {
_mightAssignSomething = _mightAssignSomething =
...@@ -173,12 +185,43 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct ...@@ -173,12 +185,43 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
return null; return null;
} }
public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node)
{
// perhaps we are passing a variable by ref and mutating it that way
_mightAssignSomething = !node.ArgumentRefKindsOpt.IsDefault;
return base.VisitDynamicInvocation(node);
}
public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpression node) public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpression node)
{ {
// perhaps we are passing a variable by ref and mutating it that way // perhaps we are passing a variable by ref and mutating it that way
_mightAssignSomething = !node.ArgumentRefKindsOpt.IsDefault; _mightAssignSomething = !node.ArgumentRefKindsOpt.IsDefault;
return base.VisitObjectCreationExpression(node); return base.VisitObjectCreationExpression(node);
} }
public override BoundNode VisitDynamicObjectCreationExpression(BoundDynamicObjectCreationExpression node)
{
_mightAssignSomething = !node.ArgumentRefKindsOpt.IsDefault;
return base.VisitDynamicObjectCreationExpression(node);
}
public override BoundNode VisitObjectInitializerMember(BoundObjectInitializerMember node)
{
_mightAssignSomething = !node.ArgumentRefKindsOpt.IsDefault;
return base.VisitObjectInitializerMember(node);
}
public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
{
_mightAssignSomething = !node.ArgumentRefKindsOpt.IsDefault;
return base.VisitIndexerAccess(node);
}
public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node)
{
_mightAssignSomething = !node.ArgumentRefKindsOpt.IsDefault;
return base.VisitDynamicIndexerAccess(node);
}
} }
protected BoundDecisionDag ShareTempsIfPossibleAndEvaluateInput( protected BoundDecisionDag ShareTempsIfPossibleAndEvaluateInput(
BoundDecisionDag decisionDag, BoundDecisionDag decisionDag,
...@@ -398,11 +441,6 @@ private bool GenerateSwitchDispatch(BoundDecisionDagNode node, HashSet<BoundDeci ...@@ -398,11 +441,6 @@ private bool GenerateSwitchDispatch(BoundDecisionDagNode node, HashSet<BoundDeci
stringEquality = _localRewriter.UnsafeGetSpecialTypeMethod(node.Syntax, SpecialMember.System_String__op_Equality); stringEquality = _localRewriter.UnsafeGetSpecialTypeMethod(node.Syntax, SpecialMember.System_String__op_Equality);
} }
if (_dagNodeLabels.TryGetValue(node, out LabelSymbol nodeLabel))
{
_loweredDecisionDag.Add(_factory.Label(nodeLabel));
}
LabelSymbol defaultLabel = GetDagNodeLabel(previous.WhenFalse); LabelSymbol defaultLabel = GetDagNodeLabel(previous.WhenFalse);
var dispatch = new BoundSwitchDispatch( var dispatch = new BoundSwitchDispatch(
node.Syntax, _tempAllocator.GetTemp(firstTest.Input), cases.ToImmutableAndFree(), defaultLabel, stringEquality); node.Syntax, _tempAllocator.GetTemp(firstTest.Input), cases.ToImmutableAndFree(), defaultLabel, stringEquality);
......
...@@ -55,6 +55,25 @@ public void Free() ...@@ -55,6 +55,25 @@ public void Free()
_map.Free(); _map.Free();
} }
#if DEBUG
public string Dump()
{
var poolElemenet = PooledStringBuilder.GetInstance();
var builder = poolElemenet.Builder;
foreach (var kv in _map)
{
builder.Append("Key: ");
builder.AppendLine(kv.Key.Dump());
builder.Append("Value: ");
builder.AppendLine(kv.Value.Dump());
}
var result = builder.ToString();
poolElemenet.Free();
return result;
}
#endif
public BoundExpression GetTemp(BoundDagTemp dagTemp) public BoundExpression GetTemp(BoundDagTemp dagTemp)
{ {
if (!_map.TryGetValue(dagTemp, out BoundExpression result)) if (!_map.TryGetValue(dagTemp, out BoundExpression result))
...@@ -365,15 +384,16 @@ private BoundExpression MakeEqual(BoundExpression loweredLiteral, BoundExpressio ...@@ -365,15 +384,16 @@ private BoundExpression MakeEqual(BoundExpression loweredLiteral, BoundExpressio
} }
} }
if (loweredInput.Kind == BoundKind.TupleLiteral && if (loweredInput.Type.IsTupleType &&
!decisionDag.TopologicallySortedNodes.Any(n => !usesOriginalInput(n)) && loweredInput.Syntax.Kind() == SyntaxKind.TupleExpression &&
false) loweredInput is BoundObjectCreationExpression expr &&
!decisionDag.TopologicallySortedNodes.Any(n => usesOriginalInput(n)))
{ {
// If the switch governing expression is a tuple literal that is not used anywhere, // If the switch governing expression is a tuple literal whose whole value is not used anywhere,
// (though perhaps its component parts are used), then we can save the component parts // (though perhaps its component parts are used), then we can save the component parts
// and assign them into temps (or perhaps user variables) to avoid the creation of // and assign them into temps (or perhaps user variables) to avoid the creation of
// the tuple altogether. // the tuple altogether.
decisionDag = RewriteTupleInput(decisionDag, (BoundTupleLiteral)loweredInput, addCode); decisionDag = RewriteTupleInput(decisionDag, expr, addCode);
} }
else else
{ {
...@@ -418,10 +438,77 @@ bool usesOriginalInput(BoundDecisionDagNode node) ...@@ -418,10 +438,77 @@ bool usesOriginalInput(BoundDecisionDagNode node)
/// <returns>A new decision dag that does not reference the input directly</returns> /// <returns>A new decision dag that does not reference the input directly</returns>
private BoundDecisionDag RewriteTupleInput( private BoundDecisionDag RewriteTupleInput(
BoundDecisionDag decisionDag, BoundDecisionDag decisionDag,
BoundTupleLiteral loweredInput, BoundObjectCreationExpression loweredInput,
Action<BoundExpression> addCode) Action<BoundExpression> addCode)
{ {
throw new NotImplementedException(); int count = loweredInput.Arguments.Length;
var tupleElementEvaluated = new bool[count];
var rewrittenDag = decisionDag.Rewrite(makeReplacement);
// If any remaining input elements remain unevaluated, evaluate them now
var originalInput = BoundDagTemp.ForOriginalInput(loweredInput.Syntax, loweredInput.Type);
for (int i = 0; i < count; i++)
{
if (!tupleElementEvaluated[i])
{
var expr = loweredInput.Arguments[i];
var field = loweredInput.Type.TupleElements[i].CorrespondingTupleField;
Debug.Assert(field != null);
var fieldFetchEvaluation = new BoundDagFieldEvaluation(expr.Syntax, field, originalInput);
var temp = new BoundDagTemp(expr.Syntax, expr.Type, fieldFetchEvaluation, 0);
storeToTemp(temp, expr);
}
}
return rewrittenDag;
void storeToTemp(BoundDagTemp temp, BoundExpression expr)
{
if ((expr.Kind == BoundKind.Parameter || expr.Kind == BoundKind.Local) && _tempAllocator.TrySetTemp(temp, expr))
{
// we've arranged to use the input value from the variable it is already stored in
}
else
{
var tempToHoldInput = _tempAllocator.GetTemp(temp);
addCode(_factory.AssignmentExpression(tempToHoldInput, expr));
}
}
BoundDecisionDagNode makeReplacement(BoundDecisionDagNode node, Func<BoundDecisionDagNode, BoundDecisionDagNode> replacement)
{
switch (node)
{
case BoundEvaluationDecisionDagNode evalNode:
if (evalNode.Evaluation is BoundDagFieldEvaluation eval &&
eval.Input.IsOriginalInput &&
eval.Field is var field &&
field.IsTupleField &&
field.CorrespondingTupleField != null &&
field.TupleElementIndex is int i)
{
if (!tupleElementEvaluated[i])
{
// Store the value in the right temp
var temp = new BoundDagTemp(eval.Syntax, field.Type, eval, 0);
BoundExpression expr = loweredInput.Arguments[i];
storeToTemp(temp, expr);
tupleElementEvaluated[i] = true;
}
return replacement(evalNode.Next);
}
Debug.Assert(!evalNode.Evaluation.Input.IsOriginalInput);
break;
case BoundTestDecisionDagNode testNode:
Debug.Assert(!testNode.Test.Input.IsOriginalInput);
break;
}
return BoundDecisionDag.TrivialReplacement(node, replacement);
}
} }
} }
} }
......
...@@ -1121,7 +1121,9 @@ .maxstack 2 ...@@ -1121,7 +1121,9 @@ .maxstack 2
}"); }");
} }
[Fact, WorkItem(20641, "https://github.com/dotnet/roslyn/issues/20641")] [Fact]
[WorkItem(20641, "https://github.com/dotnet/roslyn/issues/20641")]
[WorkItem(1395, "https://github.com/dotnet/csharplang/issues/1395")]
public void TupleSwitch01() public void TupleSwitch01()
{ {
var source = @"using System; var source = @"using System;
...@@ -1134,13 +1136,20 @@ public enum DoorState { Opened, Closed, Locked } ...@@ -1134,13 +1136,20 @@ public enum DoorState { Opened, Closed, Locked }
public enum Action { Open, Close, Lock, Unlock } public enum Action { Open, Close, Lock, Unlock }
public void Act(Action action, bool haveKey = false) public void Act0(Action action, bool haveKey = false)
{ {
Console.Write($""{State} {action}{(haveKey ? "" withKey"" : null)}""); Console.Write($""{State} {action}{(haveKey ? "" withKey"" : null)}"");
State = ChangeState0(State, action, haveKey); State = ChangeState0(State, action, haveKey);
Console.WriteLine($"" -> {State}""); Console.WriteLine($"" -> {State}"");
} }
public void Act1(Action action, bool haveKey = false)
{
Console.Write($""{State} {action}{(haveKey ? "" withKey"" : null)}"");
State = ChangeState1(State, action, haveKey);
Console.WriteLine($"" -> {State}"");
}
public static DoorState ChangeState0(DoorState state, Action action, bool haveKey = false) public static DoorState ChangeState0(DoorState state, Action action, bool haveKey = false)
{ {
switch ((state, action)) switch ((state, action))
...@@ -1172,13 +1181,23 @@ class Program ...@@ -1172,13 +1181,23 @@ class Program
static void Main(string[] args) static void Main(string[] args)
{ {
var door = new Door(); var door = new Door();
door.Act(Door.Action.Close); door.Act0(Door.Action.Close);
door.Act(Door.Action.Lock); door.Act0(Door.Action.Lock);
door.Act(Door.Action.Lock, true); door.Act0(Door.Action.Lock, true);
door.Act(Door.Action.Open); door.Act0(Door.Action.Open);
door.Act(Door.Action.Unlock); door.Act0(Door.Action.Unlock);
door.Act(Door.Action.Unlock, true); door.Act0(Door.Action.Unlock, true);
door.Act(Door.Action.Open); door.Act0(Door.Action.Open);
Console.WriteLine();
door = new Door();
door.Act1(Door.Action.Close);
door.Act1(Door.Action.Lock);
door.Act1(Door.Action.Lock, true);
door.Act1(Door.Action.Open);
door.Act1(Door.Action.Unlock);
door.Act1(Door.Action.Unlock, true);
door.Act1(Door.Action.Open);
} }
}"; }";
var expectedOutput = var expectedOutput =
...@@ -1189,132 +1208,105 @@ static void Main(string[] args) ...@@ -1189,132 +1208,105 @@ static void Main(string[] args)
Locked Unlock -> Locked Locked Unlock -> Locked
Locked Unlock withKey -> Closed Locked Unlock withKey -> Closed
Closed Open -> Opened Closed Open -> Opened
Opened Close -> Closed
Closed Lock -> Closed
Closed Lock withKey -> Locked
Locked Open -> Locked
Locked Unlock -> Locked
Locked Unlock withKey -> Closed
Closed Open -> Opened
"; ";
var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithRecursivePatterns()); var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithRecursivePatterns());
compilation.VerifyDiagnostics(); compilation.VerifyDiagnostics();
var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput);
compVerifier.VerifyIL("Door.ChangeState0", compVerifier.VerifyIL("Door.ChangeState0",
@"{ @"{
// Code size 94 (0x5e) // Code size 59 (0x3b)
.maxstack 3 .maxstack 2
.locals init (Door.DoorState V_0, //oldState .locals init (Door.DoorState V_0) //oldState
System.ValueTuple<Door.DoorState, Door.Action> V_1, IL_0000: ldarg.0
Door.Action V_2) IL_0001: stloc.0
IL_0000: ldloca.s V_1 IL_0002: ldloc.0
IL_0002: ldarg.0 IL_0003: switch (
IL_0003: ldarg.1 IL_0016,
IL_0004: call ""System.ValueTuple<Door.DoorState, Door.Action>..ctor(Door.DoorState, Door.Action)"" IL_001c,
IL_0009: ldloc.1 IL_0025)
IL_000a: ldfld ""Door.DoorState System.ValueTuple<Door.DoorState, Door.Action>.Item1"" IL_0014: br.s IL_0039
IL_000f: stloc.0 IL_0016: ldc.i4.1
IL_0010: ldloc.0 IL_0017: ldarg.1
IL_0011: switch ( IL_0018: beq.s IL_002b
IL_0024, IL_001a: br.s IL_0039
IL_0031, IL_001c: ldarg.1
IL_0041) IL_001d: brfalse.s IL_002d
IL_0022: br.s IL_005c IL_001f: ldarg.1
IL_0024: ldloc.1 IL_0020: ldc.i4.2
IL_0025: ldfld ""Door.Action System.ValueTuple<Door.DoorState, Door.Action>.Item2"" IL_0021: beq.s IL_002f
IL_002a: stloc.2 IL_0023: br.s IL_0039
IL_0025: ldc.i4.3
IL_0026: ldarg.1
IL_0027: beq.s IL_0034
IL_0029: br.s IL_0039
IL_002b: ldc.i4.1 IL_002b: ldc.i4.1
IL_002c: ldloc.2 IL_002c: ret
IL_002d: beq.s IL_004e IL_002d: ldc.i4.0
IL_002f: br.s IL_005c IL_002e: ret
IL_0031: ldloc.1 IL_002f: ldarg.2
IL_0032: ldfld ""Door.Action System.ValueTuple<Door.DoorState, Door.Action>.Item2"" IL_0030: brfalse.s IL_0039
IL_0037: stloc.2 IL_0032: ldc.i4.2
IL_0038: ldloc.2 IL_0033: ret
IL_0039: brfalse.s IL_0050 IL_0034: ldarg.2
IL_003b: ldloc.2 IL_0035: brfalse.s IL_0039
IL_003c: ldc.i4.2 IL_0037: ldc.i4.1
IL_003d: beq.s IL_0052 IL_0038: ret
IL_003f: br.s IL_005c IL_0039: ldloc.0
IL_0041: ldloc.1 IL_003a: ret
IL_0042: ldfld ""Door.Action System.ValueTuple<Door.DoorState, Door.Action>.Item2""
IL_0047: stloc.2
IL_0048: ldc.i4.3
IL_0049: ldloc.2
IL_004a: beq.s IL_0057
IL_004c: br.s IL_005c
IL_004e: ldc.i4.1
IL_004f: ret
IL_0050: ldc.i4.0
IL_0051: ret
IL_0052: ldarg.2
IL_0053: brfalse.s IL_005c
IL_0055: ldc.i4.2
IL_0056: ret
IL_0057: ldarg.2
IL_0058: brfalse.s IL_005c
IL_005a: ldc.i4.1
IL_005b: ret
IL_005c: ldloc.0
IL_005d: ret
}"); }");
compVerifier.VerifyIL("Door.ChangeState1", compVerifier.VerifyIL("Door.ChangeState1",
@"{ @"{
// Code size 104 (0x68) // Code size 67 (0x43)
.maxstack 3 .maxstack 2
.locals init (System.ValueTuple<Door.DoorState, Door.Action> V_0, .locals init (Door.DoorState V_0)
Door.DoorState V_1, IL_0000: ldarg.0
Door.Action V_2, IL_0001: switch (
Door.DoorState V_3) IL_0014,
IL_0000: ldloca.s V_0 IL_001a,
IL_0002: ldarg.0 IL_0023)
IL_0003: ldarg.1 IL_0012: br.s IL_003f
IL_0004: call ""System.ValueTuple<Door.DoorState, Door.Action>..ctor(Door.DoorState, Door.Action)"" IL_0014: ldc.i4.1
IL_0009: ldloc.0 IL_0015: ldarg.1
IL_000a: ldfld ""Door.DoorState System.ValueTuple<Door.DoorState, Door.Action>.Item1"" IL_0016: beq.s IL_0029
IL_000f: stloc.1 IL_0018: br.s IL_003f
IL_0010: ldloc.1 IL_001a: ldarg.1
IL_0011: switch ( IL_001b: brfalse.s IL_002d
IL_0024, IL_001d: ldarg.1
IL_0031, IL_001e: ldc.i4.2
IL_0041) IL_001f: beq.s IL_0031
IL_0022: br.s IL_0064 IL_0021: br.s IL_003f
IL_0024: ldloc.0 IL_0023: ldc.i4.3
IL_0025: ldfld ""Door.Action System.ValueTuple<Door.DoorState, Door.Action>.Item2"" IL_0024: ldarg.1
IL_002a: stloc.2 IL_0025: beq.s IL_0038
IL_002b: ldc.i4.1 IL_0027: br.s IL_003f
IL_002c: ldloc.2 IL_0029: ldc.i4.1
IL_002d: beq.s IL_004e IL_002a: stloc.0
IL_002f: br.s IL_0064 IL_002b: br.s IL_0041
IL_0031: ldloc.0 IL_002d: ldc.i4.0
IL_0032: ldfld ""Door.Action System.ValueTuple<Door.DoorState, Door.Action>.Item2"" IL_002e: stloc.0
IL_0037: stloc.2 IL_002f: br.s IL_0041
IL_0038: ldloc.2 IL_0031: ldarg.2
IL_0039: brfalse.s IL_0052 IL_0032: brfalse.s IL_003f
IL_003b: ldloc.2 IL_0034: ldc.i4.2
IL_003c: ldc.i4.2 IL_0035: stloc.0
IL_003d: beq.s IL_0056 IL_0036: br.s IL_0041
IL_003f: br.s IL_0064 IL_0038: ldarg.2
IL_0039: brfalse.s IL_003f
IL_003b: ldc.i4.1
IL_003c: stloc.0
IL_003d: br.s IL_0041
IL_003f: ldarg.0
IL_0040: stloc.0
IL_0041: ldloc.0 IL_0041: ldloc.0
IL_0042: ldfld ""Door.Action System.ValueTuple<Door.DoorState, Door.Action>.Item2"" IL_0042: ret
IL_0047: stloc.2
IL_0048: ldc.i4.3
IL_0049: ldloc.2
IL_004a: beq.s IL_005d
IL_004c: br.s IL_0064
IL_004e: ldc.i4.1
IL_004f: stloc.3
IL_0050: br.s IL_0066
IL_0052: ldc.i4.0
IL_0053: stloc.3
IL_0054: br.s IL_0066
IL_0056: ldarg.2
IL_0057: brfalse.s IL_0064
IL_0059: ldc.i4.2
IL_005a: stloc.3
IL_005b: br.s IL_0066
IL_005d: ldarg.2
IL_005e: brfalse.s IL_0064
IL_0060: ldc.i4.1
IL_0061: stloc.3
IL_0062: br.s IL_0066
IL_0064: ldarg.0
IL_0065: stloc.3
IL_0066: ldloc.3
IL_0067: ret
}"); }");
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册