未验证 提交 5739c71b 编写于 作者: T Tomáš Matoušek 提交者: GitHub

Fix EnC debug information emitted for patterns (#37239)

* Fix EnC debug information emitted for patterns

Update calculation of syntax offset to account for a new case when a node (a switch expression) that is associated with a variable, closure or lambda may share start offset with other node of the same kind (`expr switch { … } switch { … }`). Use the offset of the `switch` keyword instead of the starting offset of the expression to disambiguate.

Assign ordinals to variables synthesized for storing pattern values across cases. This is required to support complex patterns since we can no longer rely on the type of these variables to be distinct. This will require follow up in the IDE to disallow updating/adding/reordering the case clauses of switch expression which there an active statement is present within the switch statement. If the cases are unmodified the compiler guarantees that the order in which the synthesized variables are generated remains the same, so we can map the variables using their ordinal.

Mark all variables synthesized during lowering of switch expression as short-lived. Their lifespan is limited to the switch expression, which does not include a sequence point.

Disallow editing methods that contain switch expression. This is necessary until bugs https://github.com/dotnet/roslyn/issues/37232, https://github.com/dotnet/roslyn/issues/37237 are fixed.

* Feedback

* Update tests
上级 c4ec3baf
......@@ -157,7 +157,7 @@ private LocalDefinition LazyReturnTemp
var bodySyntax = _methodBodySyntaxOpt;
if (_ilEmitStyle == ILEmitStyle.Debug && bodySyntax != null)
{
int syntaxOffset = _method.CalculateLocalSyntaxOffset(bodySyntax.SpanStart, bodySyntax.SyntaxTree);
int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(bodySyntax), bodySyntax.SyntaxTree);
var localSymbol = new SynthesizedLocal(_method, _method.ReturnTypeWithAnnotations, SynthesizedLocalKind.FunctionReturnValue, bodySyntax);
result = _builder.LocalSlotManager.DeclareLocal(
......
......@@ -1435,13 +1435,8 @@ private string GetLocalDebugName(ILocalSymbolInternal local, out LocalDebugId lo
if (_ilEmitStyle == ILEmitStyle.Debug)
{
var syntax = local.GetDeclaratorSyntax();
int syntaxOffset = _method.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree);
// Synthesized locals emitted for switch case patterns are all associated with the switch statement
// and have distinct types. We use their types to match them, not the ordinal as the ordinal might
// change if switch cases are reordered.
int ordinal = (localKind != SynthesizedLocalKind.SwitchCasePatternMatching) ?
_synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset) : 0;
int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(syntax), syntax.SyntaxTree);
int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset);
// user-defined locals should have 0 ordinal:
Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined);
......
......@@ -13,16 +13,12 @@ private CSharpLambdaSyntaxFacts()
}
public override SyntaxNode GetLambda(SyntaxNode lambdaOrLambdaBodySyntax)
{
return LambdaUtilities.GetLambda(lambdaOrLambdaBodySyntax);
}
=> LambdaUtilities.GetLambda(lambdaOrLambdaBodySyntax);
public override SyntaxNode TryGetCorrespondingLambdaBody(
SyntaxNode previousLambdaSyntax,
SyntaxNode lambdaOrLambdaBodySyntax)
{
return LambdaUtilities.TryGetCorrespondingLambdaBody(
lambdaOrLambdaBodySyntax, previousLambdaSyntax);
}
public override SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode previousLambdaSyntax, SyntaxNode lambdaOrLambdaBodySyntax)
=> LambdaUtilities.TryGetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax, previousLambdaSyntax);
public override int GetDeclaratorPosition(SyntaxNode node)
=> LambdaUtilities.GetDeclaratorPosition(node);
}
}
......@@ -535,7 +535,7 @@ internal DebugId GetClosureId(SyntaxNode syntax, ArrayBuilder<ClosureDebugInfo>
closureId = new DebugId(closureDebugInfo.Count, _compilationState.ModuleBuilderOpt.CurrentGenerationOrdinal);
}
int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree);
int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(syntax), syntax.SyntaxTree);
closureDebugInfo.Add(new ClosureDebugInfo(syntaxOffset, closureId));
return closureId;
......
......@@ -1388,7 +1388,7 @@ private DebugId GetLambdaId(SyntaxNode syntax, ClosureKind closureKind, int clos
lambdaId = new DebugId(_lambdaDebugInfoBuilder.Count, CompilationState.ModuleBuilderOpt.CurrentGenerationOrdinal);
}
int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(lambdaOrLambdaBodySyntax.SpanStart, lambdaOrLambdaBodySyntax.SyntaxTree);
int syntaxOffset = _topLevelMethod.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(lambdaOrLambdaBodySyntax), lambdaOrLambdaBodySyntax.SyntaxTree);
_lambdaDebugInfoBuilder.Add(new LambdaDebugInfo(syntaxOffset, lambdaId, closureOrdinal));
return lambdaId;
}
......
......@@ -17,7 +17,7 @@ internal partial class LocalRewriter
/// <summary>
/// A common base class for lowering the pattern switch statement and the pattern switch expression.
/// </summary>
private class BaseSwitchLocalRewriter : PatternLocalRewriter
private abstract class BaseSwitchLocalRewriter : PatternLocalRewriter
{
/// <summary>
/// Map from switch section's syntax to the lowered code for the section. The code for a section
......@@ -37,27 +37,19 @@ private class BaseSwitchLocalRewriter : PatternLocalRewriter
/// </summary>
private readonly PooledDictionary<BoundDecisionDagNode, LabelSymbol> _dagNodeLabels = PooledDictionary<BoundDecisionDagNode, LabelSymbol>.GetInstance();
/// <summary>
/// True if we are translating a switch statement. This affects sequence points (a when clause gets
/// a sequence point in a switch statement, but not in a switch expression).
/// </summary>
private readonly bool _isSwitchStatement;
protected BaseSwitchLocalRewriter(
SyntaxNode node,
LocalRewriter localRewriter,
ImmutableArray<SyntaxNode> arms,
bool isSwitchStatement)
ImmutableArray<SyntaxNode> arms)
: base(node, localRewriter)
{
this._isSwitchStatement = isSwitchStatement;
foreach (var arm in arms)
{
var armBuilder = ArrayBuilder<BoundStatement>.GetInstance();
// We start each switch block of a switch statement with a hidden sequence point so that
// we do not appear to be in the previous switch block when we begin.
if (isSwitchStatement)
if (IsSwitchStatement)
armBuilder.Add(_factory.HiddenSequencePoint());
_switchArms.Add(arm, armBuilder);
......@@ -289,7 +281,7 @@ public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess no
// In a switch statement, there is a hidden sequence point after evaluating the input at the start of
// the code to handle the decision dag. This is necessary so that jumps back from a `when` clause into
// the decision dag do not appear to jump back up to the enclosing construct.
if (_isSwitchStatement)
if (IsSwitchStatement)
result.Add(_factory.HiddenSequencePoint());
return decisionDag;
......@@ -303,9 +295,9 @@ public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess no
Debug.Assert(this._loweredDecisionDag.IsEmpty());
ComputeLabelSet(decisionDag);
LowerDecisionDagCore(decisionDag);
ImmutableArray<BoundStatement> loweredDag = this._loweredDecisionDag.ToImmutableAndFree();
ImmutableDictionary<SyntaxNode, ImmutableArray<BoundStatement>> switchSections = this._switchArms.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableAndFree());
this._switchArms.Clear();
ImmutableArray<BoundStatement> loweredDag = _loweredDecisionDag.ToImmutableAndFree();
var switchSections = _switchArms.ToImmutableDictionary(kv => kv.Key, kv => kv.Value.ToImmutableAndFree());
_switchArms.Clear();
return (loweredDag, switchSections);
}
......@@ -343,7 +335,7 @@ private void LowerDecisionDagCore(BoundDecisionDag decisionDag)
continue;
}
if (this._dagNodeLabels.TryGetValue(node, out LabelSymbol label))
if (_dagNodeLabels.TryGetValue(node, out LabelSymbol label))
{
_loweredDecisionDag.Add(_factory.Label(label));
}
......@@ -581,7 +573,7 @@ private void LowerWhenClause(BoundWhenDecisionDagNode whenClause)
BoundStatement conditionalGoto = _factory.ConditionalGoto(_localRewriter.VisitExpression(whenClause.WhenExpression), trueLabel, jumpIfTrue: true);
// Only add instrumentation (such as a sequence point) if the node is not compiler-generated.
if (_isSwitchStatement && !whenClause.WhenExpression.WasCompilerGenerated && _localRewriter.Instrument)
if (IsSwitchStatement && !whenClause.WhenExpression.WasCompilerGenerated && _localRewriter.Instrument)
{
conditionalGoto = _localRewriter._instrumenter.InstrumentSwitchWhenClauseConditionalGotoBody(whenClause.WhenExpression, conditionalGoto);
}
......@@ -592,7 +584,7 @@ private void LowerWhenClause(BoundWhenDecisionDagNode whenClause)
// We hide the jump back into the decision dag, as it is not logically part of the when clause
BoundStatement jump = _factory.Goto(GetDagNodeLabel(whenFalse));
sectionBuilder.Add(_isSwitchStatement ? _factory.HiddenSequencePoint(jump) : jump);
sectionBuilder.Add(IsSwitchStatement ? _factory.HiddenSequencePoint(jump) : jump);
}
else
{
......@@ -618,7 +610,7 @@ private void LowerDecisionDagNode(BoundDecisionDagNode node, BoundDecisionDagNod
// We add a hidden sequence point after the evaluation's side-effect, which may be a call out
// to user code such as `Deconstruct` or a property get, to permit edit-and-continue to
// synchronize on changes.
if (_isSwitchStatement)
if (IsSwitchStatement)
_loweredDecisionDag.Add(_factory.HiddenSequencePoint());
if (nextNode != evaluationNode.Next)
......
......@@ -18,7 +18,7 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node
return result;
}
private class IsPatternExpressionLocalRewriter : PatternLocalRewriter
private sealed class IsPatternExpressionLocalRewriter : PatternLocalRewriter
{
/// <summary>
/// Accumulates side-effects that come before the next conjunct.
......@@ -35,10 +35,12 @@ private class IsPatternExpressionLocalRewriter : PatternLocalRewriter
public IsPatternExpressionLocalRewriter(SyntaxNode node, LocalRewriter localRewriter)
: base(node, localRewriter)
{
this._conjunctBuilder = ArrayBuilder<BoundExpression>.GetInstance();
this._sideEffectBuilder = ArrayBuilder<BoundExpression>.GetInstance();
_conjunctBuilder = ArrayBuilder<BoundExpression>.GetInstance();
_sideEffectBuilder = ArrayBuilder<BoundExpression>.GetInstance();
}
protected override bool IsSwitchStatement => false;
public new void Free()
{
_conjunctBuilder.Free();
......
......@@ -16,24 +16,26 @@ internal partial class LocalRewriter
{
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
return SwitchLocalRewriter.Rewrite(this, node);
return SwitchStatementLocalRewriter.Rewrite(this, node);
}
private class SwitchLocalRewriter : BaseSwitchLocalRewriter
private sealed class SwitchStatementLocalRewriter : BaseSwitchLocalRewriter
{
/// <summary>
/// A map from section syntax to the first label in that section.
/// </summary>
private readonly Dictionary<SyntaxNode, LabelSymbol> _sectionLabels = PooledDictionary<SyntaxNode, LabelSymbol>.GetInstance();
protected override bool IsSwitchStatement => true;
public static BoundStatement Rewrite(LocalRewriter localRewriter, BoundSwitchStatement node)
{
var rewriter = new SwitchLocalRewriter(node, localRewriter);
var rewriter = new SwitchStatementLocalRewriter(node, localRewriter);
BoundStatement result = rewriter.LowerSwitchStatement(node);
rewriter.Free();
return result;
}
/// <summary>
/// A map from section syntax to the first label in that section.
/// </summary>
private Dictionary<SyntaxNode, LabelSymbol> _sectionLabels = PooledDictionary<SyntaxNode, LabelSymbol>.GetInstance();
/// <summary>
/// We revise the returned label for a leaf so that all leaves in the same switch section are given the same label.
/// This enables the switch emitter to produce better code.
......@@ -62,8 +64,8 @@ protected override LabelSymbol GetDagNodeLabel(BoundDecisionDagNode dag)
return result;
}
private SwitchLocalRewriter(BoundSwitchStatement node, LocalRewriter localRewriter)
: base(node.Syntax, localRewriter, node.SwitchSections.SelectAsArray(section => section.Syntax), isSwitchStatement: true)
private SwitchStatementLocalRewriter(BoundSwitchStatement node, LocalRewriter localRewriter)
: base(node.Syntax, localRewriter, node.SwitchSections.SelectAsArray(section => section.Syntax))
{
}
......
......@@ -16,7 +16,7 @@ internal sealed partial class LocalRewriter
/// <summary>
/// A common base class for lowering constructs that use pattern-matching.
/// </summary>
private class PatternLocalRewriter
private abstract class PatternLocalRewriter
{
protected readonly LocalRewriter _localRewriter;
protected readonly SyntheticBoundNodeFactory _factory;
......@@ -24,27 +24,39 @@ private class PatternLocalRewriter
public PatternLocalRewriter(SyntaxNode node, LocalRewriter localRewriter)
{
this._localRewriter = localRewriter;
this._factory = localRewriter._factory;
this._tempAllocator = new DagTempAllocator(_factory, node);
_localRewriter = localRewriter;
_factory = localRewriter._factory;
_tempAllocator = new DagTempAllocator(_factory, node, IsSwitchStatement);
}
/// <summary>
/// True if this is a rewriter for a switch statement. This affects
/// - sequence points
/// When clause gets a sequence point in a switch statement, but not in a switch expression.
/// - synthesized local variable kind
/// The temp variables must be long lived in a switch statement since their lifetime spans across sequence points.
/// </summary>
protected abstract bool IsSwitchStatement { get; }
public void Free()
{
_tempAllocator.Free();
}
public class DagTempAllocator
public sealed class DagTempAllocator
{
private readonly SyntheticBoundNodeFactory _factory;
private readonly PooledDictionary<BoundDagTemp, BoundExpression> _map = PooledDictionary<BoundDagTemp, BoundExpression>.GetInstance();
private readonly ArrayBuilder<LocalSymbol> _temps = ArrayBuilder<LocalSymbol>.GetInstance();
private readonly SyntaxNode _node;
public DagTempAllocator(SyntheticBoundNodeFactory factory, SyntaxNode node)
private readonly bool _isSwitchStatement;
public DagTempAllocator(SyntheticBoundNodeFactory factory, SyntaxNode node, bool isSwitchStatement)
{
this._factory = factory;
this._node = node;
_factory = factory;
_node = node;
_isSwitchStatement = isSwitchStatement;
}
public void Free()
......@@ -76,7 +88,8 @@ public BoundExpression GetTemp(BoundDagTemp dagTemp)
{
if (!_map.TryGetValue(dagTemp, out BoundExpression result))
{
LocalSymbol temp = _factory.SynthesizedLocal(dagTemp.Type, syntax: _node, kind: SynthesizedLocalKind.SwitchCasePatternMatching);
var kind = _isSwitchStatement ? SynthesizedLocalKind.SwitchCasePatternMatching : SynthesizedLocalKind.LoweringTemp;
LocalSymbol temp = _factory.SynthesizedLocal(dagTemp.Type, syntax: _node, kind: kind);
result = _factory.Local(temp);
_map.Add(dagTemp, result);
_temps.Add(temp);
......
......@@ -20,13 +20,15 @@ public override BoundNode VisitConvertedSwitchExpression(BoundConvertedSwitchExp
return SwitchExpressionLocalRewriter.Rewrite(this, node);
}
private class SwitchExpressionLocalRewriter : BaseSwitchLocalRewriter
private sealed class SwitchExpressionLocalRewriter : BaseSwitchLocalRewriter
{
private SwitchExpressionLocalRewriter(BoundConvertedSwitchExpression node, LocalRewriter localRewriter)
: base(node.Syntax, localRewriter, node.SwitchArms.SelectAsArray(arm => arm.Syntax), isSwitchStatement: false)
: base(node.Syntax, localRewriter, node.SwitchArms.SelectAsArray(arm => arm.Syntax))
{
}
protected override bool IsSwitchStatement => false;
public static BoundExpression Rewrite(LocalRewriter localRewriter, BoundConvertedSwitchExpression node)
{
var rewriter = new SwitchExpressionLocalRewriter(node, localRewriter);
......@@ -55,7 +57,7 @@ private BoundExpression LowerSwitchExpression(BoundConvertedSwitchExpression nod
// decision tree, so the code in result is unreachable at this point.
// Lower each switch expression arm
LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.SwitchCasePatternMatching);
LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.LoweringTemp);
LabelSymbol afterSwitchExpression = _factory.GenerateLabel("afterSwitchExpression");
foreach (BoundSwitchExpressionArm arm in node.SwitchArms)
{
......
......@@ -543,7 +543,6 @@ public override BoundNode VisitAwaitExpression(BoundAwaitExpression node)
public override BoundNode VisitSpillSequence(BoundSpillSequence node)
{
Debug.Assert(node.Locals.All(l => l.SynthesizedKind.IsLongLived()));
var builder = new BoundSpillSequenceBuilder();
// Ensure later errors (e.g. in async rewriting) are associated with the correct node.
......
......@@ -471,7 +471,7 @@ private BoundExpression HoistRefInitialization(SynthesizedLocal local, BoundAssi
if (F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug)
{
awaitSyntaxOpt = (AwaitExpressionSyntax)local.GetDeclaratorSyntax();
syntaxOffset = this.OriginalMethod.CalculateLocalSyntaxOffset(awaitSyntaxOpt.SpanStart, awaitSyntaxOpt.SyntaxTree);
syntaxOffset = OriginalMethod.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(awaitSyntaxOpt), awaitSyntaxOpt.SyntaxTree);
}
else
{
......
......@@ -175,7 +175,7 @@ protected BoundStatement Rewrite()
// EnC: When emitting the baseline (gen 0) the id is stored in a custom debug information attached to the kickoff method.
// When emitting a delta the id is only used to map to the existing field in the previous generation.
SyntaxNode declaratorSyntax = local.GetDeclaratorSyntax();
int syntaxOffset = this.method.CalculateLocalSyntaxOffset(declaratorSyntax.SpanStart, declaratorSyntax.SyntaxTree);
int syntaxOffset = method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(declaratorSyntax), declaratorSyntax.SyntaxTree);
int ordinal = synthesizedLocalOrdinals.AssignLocalOrdinal(synthesizedKind, syntaxOffset);
id = new LocalDebugId(syntaxOffset, ordinal);
......
......@@ -460,6 +460,16 @@ internal static bool IsClosureScope(SyntaxNode node)
return false;
}
/// <summary>
/// Given a node that represents a variable declaration, lambda or a closure scope return the position to be used to calculate
/// the node's syntax offset with respect to its containing member.
/// </summary>
internal static int GetDeclaratorPosition(SyntaxNode node)
{
// To differentiate between nested switch expressions that start at the same offset, use the offset of the `switch` keyword.
return (node is SwitchExpressionSyntax switchExpression) ? switchExpression.SwitchKeyword.SpanStart : node.SpanStart;
}
private static SyntaxNode GetLocalFunctionBody(LocalFunctionStatementSyntax localFunctionStatementSyntax)
{
return (SyntaxNode)localFunctionStatementSyntax.Body ?? localFunctionStatementSyntax.ExpressionBody?.Expression;
......
......@@ -303,30 +303,25 @@ .maxstack 1
}");
// Debug
compilation = CompileAndVerify(source, expectedOutput: "True Branch taken", options: TestOptions.DebugExe);
compilation.VerifyIL("C.Main", @"{
// Code size 31 (0x1f)
compilation.VerifyIL("C.Main", @"
{
// Code size 27 (0x1b)
.maxstack 1
.locals init (object V_0,
bool V_1,
object V_2)
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldc.i4.1
IL_0004: call ""void System.Console.Write(bool)""
IL_0009: nop
IL_000a: ldnull
IL_000b: stloc.2
IL_000c: ldc.i4.1
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: brfalse.s IL_001e
IL_0011: nop
IL_0012: ldstr "" Branch taken""
IL_0017: call ""void System.Console.Write(string)""
IL_001c: nop
IL_001d: nop
IL_001e: ret
IL_0001: ldc.i4.1
IL_0002: call ""void System.Console.Write(bool)""
IL_0007: nop
IL_0008: ldc.i4.1
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: brfalse.s IL_001a
IL_000d: nop
IL_000e: ldstr "" Branch taken""
IL_0013: call ""void System.Console.Write(string)""
IL_0018: nop
IL_0019: nop
IL_001a: ret
}");
}
......@@ -362,29 +357,24 @@ .maxstack 1
// Debug
compilation = CompileAndVerify(source, expectedOutput: "True Branch taken", options: TestOptions.DebugExe);
compilation.VerifyIL("C.Main", @"{
// Code size 31 (0x1f)
// Code size 27 (0x1b)
.maxstack 1
.locals init (string V_0,
bool V_1,
string V_2)
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldc.i4.1
IL_0004: call ""void System.Console.Write(bool)""
IL_0009: nop
IL_000a: ldnull
IL_000b: stloc.2
IL_000c: ldc.i4.1
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: brfalse.s IL_001e
IL_0011: nop
IL_0012: ldstr "" Branch taken""
IL_0017: call ""void System.Console.Write(string)""
IL_001c: nop
IL_001d: nop
IL_001e: ret
IL_0001: ldc.i4.1
IL_0002: call ""void System.Console.Write(bool)""
IL_0007: nop
IL_0008: ldc.i4.1
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: brfalse.s IL_001a
IL_000d: nop
IL_000e: ldstr "" Branch taken""
IL_0013: call ""void System.Console.Write(string)""
IL_0018: nop
IL_0019: nop
IL_001a: ret
}");
}
......
......@@ -570,34 +570,31 @@ public static decimal M(decimal d)
var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput);
compVerifier.VerifyIL("C.M1",
@"{
// Code size 39 (0x27)
// Code size 37 (0x25)
.maxstack 6
.locals init (bool V_0,
decimal V_1,
int V_2)
int V_1)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call ""decimal C.M(decimal)""
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: ldc.i4.s 10
IL_0007: ldc.i4.s 10
IL_0009: ldc.i4.0
IL_000a: ldc.i4.0
IL_000b: ldc.i4.0
IL_000c: ldc.i4.0
IL_000d: ldc.i4.0
IL_000e: ldc.i4.1
IL_000f: newobj ""decimal..ctor(int, int, int, bool, byte)""
IL_0014: call ""bool decimal.op_Equality(decimal, decimal)""
IL_0019: stloc.0
IL_001a: ldloc.0
IL_001b: brfalse.s IL_0021
IL_001d: ldc.i4.1
IL_001e: stloc.2
IL_001f: br.s IL_0025
IL_0021: ldc.i4.0
IL_0022: stloc.2
IL_0023: br.s IL_0025
IL_0025: ldloc.2
IL_0026: ret
IL_000c: ldc.i4.1
IL_000d: newobj ""decimal..ctor(int, int, int, bool, byte)""
IL_0012: call ""bool decimal.op_Equality(decimal, decimal)""
IL_0017: stloc.0
IL_0018: ldloc.0
IL_0019: brfalse.s IL_001f
IL_001b: ldc.i4.1
IL_001c: stloc.1
IL_001d: br.s IL_0023
IL_001f: ldc.i4.0
IL_0020: stloc.1
IL_0021: br.s IL_0023
IL_0023: ldloc.1
IL_0024: ret
}");
compVerifier.VerifyIL("C.M2",
@"{
......
......@@ -8281,27 +8281,24 @@ .maxstack 1
expectedOutput: "RemoveEmptyEntries");
compVerifier.VerifyIL("Program.Main",
@"{
// Code size 25 (0x19)
// Code size 23 (0x17)
.maxstack 1
.locals init (object V_0, //o
bool V_1,
System.StringSplitOptions V_2)
bool V_1)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.2
IL_0003: ldloc.2
IL_0004: box ""System.StringSplitOptions""
IL_0009: stloc.0
IL_000a: ldc.i4.1
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: brfalse.s IL_0018
IL_000f: nop
IL_0010: ldloc.0
IL_0011: call ""void System.Console.WriteLine(object)""
IL_0016: nop
IL_0017: nop
IL_0018: ret
IL_0002: box ""System.StringSplitOptions""
IL_0007: stloc.0
IL_0008: ldc.i4.1
IL_0009: stloc.1
IL_000a: ldloc.1
IL_000b: brfalse.s IL_0016
IL_000d: nop
IL_000e: ldloc.0
IL_000f: call ""void System.Console.WriteLine(object)""
IL_0014: nop
IL_0015: nop
IL_0016: ret
}"
);
}
......
......@@ -3801,42 +3801,38 @@ class C
diff1.VerifyIL("C.F", @"
{
// Code size 52 (0x34)
// Code size 47 (0x2f)
.maxstack 2
.locals init (bool V_0,
[int] V_1,
[int] V_2,
int V_3,
int V_4)
int V_2)
-IL_0000: nop
-IL_0001: ldarg.0
IL_0002: isinst ""bool""
IL_0007: brtrue.s IL_0021
IL_0007: brtrue.s IL_001f
IL_0009: ldarg.0
IL_000a: isinst ""int""
IL_000f: brfalse.s IL_001e
IL_000f: brfalse.s IL_001c
IL_0011: ldarg.0
IL_0012: unbox.any ""int""
IL_0017: stloc.3
IL_0018: ldloc.3
IL_0019: ldc.i4.0
IL_001a: ceq
IL_001c: br.s IL_001f
IL_001e: ldc.i4.0
IL_001f: br.s IL_0022
IL_0021: ldc.i4.1
IL_0022: stloc.0
~IL_0023: ldloc.0
IL_0024: brfalse.s IL_002c
-IL_0026: nop
-IL_0027: ldc.i4.0
IL_0028: stloc.s V_4
IL_002a: br.s IL_0031
-IL_002c: ldc.i4.1
IL_002d: stloc.s V_4
IL_002f: br.s IL_0031
-IL_0031: ldloc.s V_4
IL_0033: ret
IL_0017: ldc.i4.0
IL_0018: ceq
IL_001a: br.s IL_001d
IL_001c: ldc.i4.0
IL_001d: br.s IL_0020
IL_001f: ldc.i4.1
IL_0020: stloc.0
~IL_0021: ldloc.0
IL_0022: brfalse.s IL_0029
-IL_0024: nop
-IL_0025: ldc.i4.0
IL_0026: stloc.2
IL_0027: br.s IL_002d
-IL_0029: ldc.i4.1
IL_002a: stloc.2
IL_002b: br.s IL_002d
-IL_002d: ldloc.2
IL_002e: ret
}", methodToken: diff1.UpdatedMethods.Single());
}
......
......@@ -92,12 +92,12 @@ internal sealed class EncVariableSlotAllocator : VariableSlotAllocator
public override DebugId? MethodId => _methodId;
private int CalculateSyntaxOffsetInPreviousMethod(int position, SyntaxTree tree)
private int CalculateSyntaxOffsetInPreviousMethod(SyntaxNode node)
{
// Note that syntax offset of a syntax node contained in a lambda body is calculated by the containing top-level method,
// not by the lambda method. The offset is thus relative to the top-level method body start. We can thus avoid mapping
// the current lambda symbol or body to the corresponding previous lambda symbol or body, which is non-trivial.
return _previousTopLevelMethod.CalculateLocalSyntaxOffset(position, tree);
return _previousTopLevelMethod.CalculateLocalSyntaxOffset(_lambdaSyntaxFacts.GetDeclaratorPosition(node), node.SyntaxTree);
}
public override void AddPreviousLocals(ArrayBuilder<Cci.ILocalDefinition> builder)
......@@ -120,11 +120,11 @@ private bool TryGetPreviousLocalId(SyntaxNode currentDeclarator, LocalDebugId cu
SyntaxNode previousDeclarator = _syntaxMapOpt(currentDeclarator);
if (previousDeclarator == null)
{
previousId = default(LocalDebugId);
previousId = default;
return false;
}
int syntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousDeclarator.SpanStart, previousDeclarator.SyntaxTree);
int syntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousDeclarator);
previousId = new LocalDebugId(syntaxOffset, currentId.Ordinal);
return true;
}
......@@ -246,7 +246,7 @@ private bool TryGetPreviousSyntaxOffset(SyntaxNode currentSyntax, out int previo
return false;
}
previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax.SpanStart, previousSyntax.SyntaxTree);
previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax);
return true;
}
......@@ -284,7 +284,7 @@ private bool TryGetPreviousLambdaSyntaxOffset(SyntaxNode lambdaOrLambdaBodySynta
previousSyntax = previousLambdaSyntax;
}
previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax.SpanStart, previousSyntax.SyntaxTree);
previousSyntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousSyntax);
return true;
}
......
......@@ -15,5 +15,11 @@ internal abstract class LambdaSyntaxFacts
/// JoinClause1.GetCorrespondingLambdaBody(JoinClause2.RightExpression) returns JoinClause1.RightExpression.
/// </summary>
public abstract SyntaxNode TryGetCorrespondingLambdaBody(SyntaxNode previousLambdaSyntax, SyntaxNode lambdaOrLambdaBodySyntax);
/// <summary>
/// Given a node that represents a variable declaration, lambda or a closure scope return the position to be used to calculate
/// the node's syntax offset with respect to its containing member.
/// </summary>
public abstract int GetDeclaratorPosition(SyntaxNode node);
}
}
......@@ -18,5 +18,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit
Public Overrides Function TryGetCorrespondingLambdaBody(previousLambdaSyntax As SyntaxNode, lambdaOrLambdaBodySyntax As SyntaxNode) As SyntaxNode
Return LambdaUtilities.GetCorrespondingLambdaBody(lambdaOrLambdaBodySyntax, previousLambdaSyntax)
End Function
Public Overrides Function GetDeclaratorPosition(node As SyntaxNode) As Integer
Return node.SpanStart
End Function
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册