Skip to content

Latest commit

 

History

History
57 lines (48 loc) · 3.71 KB

Expression Breakpoints.md

File metadata and controls

57 lines (48 loc) · 3.71 KB

Expression Breakpoints

The Roslyn C# compiler has infrastructure in place to produce breakpoints within expressions rather than only at statement boundaries. The first expression form that supports breakpoints is the switch expression. Because breakpoints locations correspond to sequence points in source, this is accomplished by producing additional sequence points. Sequence points are constrained to appear only at locations in the code where the evaluation stack is empty, so any expressions that have breakpoint support are translated using a BoundSpillSequence. That is processed by a compiler pass SpillSequenceSpiller to ensure that it only occurs where the stack is empty.

Lowering of an expression that requires breakpoint support is done with the assistance of three new sequence-point bound statement nodes, which are placed in the BoundSpillSequence

  <!--
    This is used to save the debugger's idea of what the enclosing sequence
    point is at this location in the code so that it can be restored later
    by a BoundRestorePreviousSequencePoint node. When this statement appears,
    the previous non-hidden sequence point is saved and associated with the
    given Identifier.
    -->
  <Node Name="BoundSavePreviousSequencePoint" Base="BoundStatement">
    <Field Name="Identifier" Type="object"/>
  </Node>

  <!--
    This is used to restore the debugger's idea of what the enclosing statement
    is to some previous location without introducing a place where a breakpoint
    would cause the debugger to stop. The identifier must have
    previously been given in a BoundSavePreviousSequencePoint statement. This is used
    to implement breakpoints within expressions (e.g. a switch expression).
    -->
  <Node Name="BoundRestorePreviousSequencePoint" Base="BoundStatement">
    <Field Name="Identifier" Type="object"/>
  </Node>

  <!--
    This is used to set the debugger's idea of what the enclosing statement
    is without causing the debugger to stop here when single stepping.
    -->
  <Node Name="BoundStepThroughSequencePoint" Base="BoundStatement">
    <Field Name="Span" Type="TextSpan"/>
  </Node>

A BoundSavePreviousSequencePoint is used to save the "current statement" information at the start of the expression, so that it can be restored after the expression. This is needed so that code that appears after the expression does not appear, in the debugger, to be executing the instrumented expression.

Both the BoundRestorePreviousSequencePoint and BoundStepThroughSequencePoint are intended to change the debugger's idea of what the current statement is without triggering a location where single-stepping would cause the debugger to stop at that location. That is accomplished by the generation of the following sequence:

    ldc.i4 1
    brtrue.s L
    // sequence point
    nop
  L:
    // hidden sequence point

This can be seen, for example, in test SwitchExpressionSequencePoints

The purpose of this instruction sequence is to cause there to be an unreachable IL opcode (the nop) having a sequence point, followed by a hidden sequence point to prevent the debugger from stopping at the following location. However, once it is executing the following code, the debugger's idea of the program's "current source location" will appear to be that location mentioned in the sequence point.

A BoundStepThroughSequencePoint also modifies the debugger's view of the "enclosing statement", but without creating a location where a breakpoint can be set. While evaluating the state machine of a switch expression, this is used to make the "current statement" appear to be the switch expression.