-
Notifications
You must be signed in to change notification settings - Fork 804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Debug revamp, make all debug points explicit #12432
Conversation
|
Yes indeed. There are_ three different cases
I've added all these to the manual test matrix in
let testSimpleForEachListLoopWithOneStatement (inp:int list) =
for x in inp do
printfn $"hello, x = {x}"
I've marked this WIP until we get those sorted out |
It looks like we also optimize |
I have started working on the "async" and other computation expression debugging problems for This has been a lot more work than I expected and eventually I decided to work from the ground up, looking at our debugging for very simple computation expressions and indeed for any inlined code, not just for Anyway, this is still work in progress but I believe I'm making progress slowly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been going through the codegen changes file by file, marking them for whether the changes are fully expected or not
tests/fsharpqa/Source/Optimizations/ForLoop/NoIEnumerable03.il.bsl
Outdated
Show resolved
Hide resolved
tests/fsharpqa/Source/Optimizations/ForLoop/NoIEnumerable02.il.bsl
Outdated
Show resolved
Hide resolved
tests/fsharpqa/Source/Optimizations/ForLoop/NoIEnumerable01.il.bsl
Outdated
Show resolved
Hide resolved
tests/fsharpqa/Source/Optimizations/ForLoop/NoAllocationOfTuple01.il.bsl
Show resolved
Hide resolved
I think this should go green now - struggled with one failing test but should be good |
OK, all green now and ready for review |
@KevinRansom @vzarytovskii @brettfo @TIHan @auduchinok Can we have reviews on this? Thanks! |
@@ -2579,6 +2579,7 @@ and p_expr expr st = | |||
| Expr.TyChoose (a, b, c) -> p_byte 12 st; p_tup3 p_tyar_specs p_expr p_dummy_range (a, b, c) st | |||
| Expr.Quote (ast, _, _, m, ty) -> p_byte 13 st; p_tup3 p_expr p_dummy_range p_ty (ast, m, ty) st | |||
| Expr.WitnessArg (traitInfo, m) -> p_byte 14 st; p_trait traitInfo st; p_dummy_range m st | |||
| Expr.DebugPoint (_, innerExpr) -> p_expr innerExpr st |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so we are not serializing Expr.DebugPoint
. Does this mean that inline functions won't have this information when they are used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so we are not serializing Expr.DebugPoint. Does this mean that inline functions won't have this information when they are used?
That's correct, inline data carries no source information at present, and hasn't done so previously.
I did experiments in allowing inline data to carry source, but the call stacks get badly messed up, because the calling stack frame is present but the location is the inline code, and it's really hard to work out what's going on.
@@ -4731,7 +4732,19 @@ type Expr = | |||
| WitnessArg _ -> "WitnessArg(..)" | |||
| TyChoose _ -> "TyChoose(..)" | |||
| Link e -> "Link(" + e.Value.ToDebugString(depth) + ")" | |||
|
|||
| DebugPoint (DebugPointAtLeafExpr.Yes m, e) -> sprintf "DebugPoint(%s, " (m.ToShortString()) + e.ToDebugString(depth) + ")" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a missing case here for DebugPointAtLeafExpr.No
- which I assume we should just print the inner expression at that point:
| DebugPoint (DebugPointAtLeafExpr.No, e) -> e.ToDebugString(depth + 1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's actually no No
case for DebugPointAtLeafExpr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes look good! Just a few comments.
@dsyme |
@dsyme Please consider a situation where a new FCS is used with an older compiler/SDK or a new compiler is used together with an older FCS. Will |
It is hard to tell what we should do here, and in this PR I think we should just focus on getting the experience fully fixed, rather than backwards compat for previous (buggy) versions. I'm confident the ValidateBreakpointLocationImpl will be good enough - what you'll get is debug locations being "extended" when debugging actually attaches from say the first part of a pipeline to the full pipeline, or similarly for boolean logic. If we were to try to do compat going forward, language version isn't the right thing to switch on - we should switch on the compiler version used to generate the target assembly being debugged. But of course different assemblies in an application could have been made with different compiler versions. C# emits the full DLL verisons and command line compilation switches into the PDB for each assembly, and perhaps some compiler version information as well, I'm not sure. so ValidateBreakpointLocationImpl would need to be passed in this kind of information. But prior to debugging you don't actually have this anyway, so the editor has to "guess" from project settings (which may or may not match the actual target binary, and may not even be available for out-of-project source). |
Yes, and Yes - I'll add it now |
@vzarytovskii I've updated the docs in this PR, thanks |
@@ -1047,6 +1050,12 @@ type SynExpr = | |||
synStringKind :SynStringKind * | |||
range: range | |||
|
|||
/// Debug points arising from computation expressions | |||
| DebugPoint of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dsyme Since these expressions are only created during type check stage, could it be better to produce typed tree nodes instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are produced by desugaring computation expressions, which is done as SyntaxTree -> SyntaxTree. It's not easy to internalise the individual nodes produced
Hello! Does this PR fix breakpoints failing to bind in If yes, is there a way to tell in which version (of VS? F#?) this will be released? |
It's a good question - do you have a more specific repro of this problem please? |
Just tested with the following demo: [<EntryPoint>]
let main args =
let test1 () =
[ 3; 2; 1 ]
|> List.map (fun x -> x + 10)
|> List.sort
let test2 = [
for x in test1() do
let xx = x * x
printf "test"
xx
]
let test3 = [
for x in test1() ->
let xx = x * x
printf "test"
xx
]
printf "The end" Notice that:
I'm using Visual Studio Community 2022 (64-bit) - Version 17.1.0, and it's a .NET 6.0 project. |
@mlaily Thanks for the repro, I'll open a separate issue on this |
This contains a significant revamp of parts of our debug point implementation.
Previous "implicit" debug points (for things like method calls) are now kept as explicit nodes in the expression tree
Expr.DebugPoint
. This makes them muchmore reliable as the tree undergoes transformations, e.g. inlining of a method call. This change should have been made long ago as many of our debug problems were related to the lack of explicit representation of the information here.Debug points for sequential code and targets of matches/conditionals and leaf expressions (e.g. method calls) are removed in favour of explicit debug points
Debugging for 'for' loops was broken in various ways, this is a systematic deep sweep over the whole matrix described below, including for computation expressions. As part of this, the debug point on the
in
orto
keyword for a for loop is made more explicit in the code. For a a computation expression (, e.g. a task) the debug point must be propagated to inline code, and this is done through a potentially-general mechanism__debugPoint
that allows inlined code to lay down "named" debug points associated with the context into which the code is inlined. However currently only__debugPoint "ForLoop.InOrToKeyword"
is supportedDebug points for the "with" and "finally" parts of try/with and try/finally in computation expressions were always missing. This adds them.
We add DebuggerNonUserCodeAttribute and GeneratedCodeAttribute attributes to any lambda generated method where the code has no debug points, rather than adding a false debug point. This covers a range of "micro-lambdas" generated for computation expressions.
Two optimizations were not preservig debug point sequencing. For example, for
Here the debug point for binding "x" and "y" were being switched around. There was also this case:
Here the debug point associated with the binding for
x
was being lost. It's now preserved - note this results in additional debug points andnop
in release code, but that is what we expect and is not a performance problem since the JIT ignores thenop
and debug points unless actually debugging.We add debug points either side of
e1 && e2
ande1 || e2
- that is, these constructs are now considered part of control-flow.FCS API is adjusted to reveal various debug points
Cleanup
The changes stem mainly from the above, with a little cleanup:
primMkCond
,mkCond
,AddResultTarget
,mkSequential
,mkThenDoSequential
,mkSequentials
and friends no longer needs to take an explicit debug point, because the debug points are explicit in the branchesTOp.For
renamed toTOp.IntegerForLoop
Expr.Sequential
no longer takes a debug point, since they are explicit on each of the leaf parts etc. of the sequentialexpr.Range
moved toTypedTree.fs
implementationExpressionWithSignature
renamed toimplExprWithSig
DebugPoint
associated with a method is renamed toDebugRange
since that's what it is . However this data is ignored in Portable PDB in any casemkSynBinding
more explicitSPAlways
andSPSuppress
and a whole host of difficult logic for debug point placement is removed from IlxGen.fsTest baselines
As usual these changes are overly invasive but I've been going through and checking them clearly.
nop
are addedBoolean logic
See example below
a.-.Microsoft.Visual.Studio.Administrator.2022-02-04.13-12-49.mp4
for loops
I looked into #12410 and take a deeper look at how we do debugging for
for .. in ... do
for-each loops, including all the variations we emitGetEnumerator
pattern (e.g. a method, not IEnumerable)IEnumerable<_>
All of which can occur in any of these:
async { ... }
task { .. }
seq { ... }
[ ... ]
/array `[| ... |]cancellable
I also looked carefully where C# puts debug points. This is a set of fixes related to that.
Before - one debug point across the "for ... do". This is mistaken, as it is triggered multiple times on first loop entry (e.g. when the enumeration is computed) and fails to get the debug points right when the loop body is a single statement. If you look carefully at the video of repeated F10 stepping this shows several bugs (most of them "double hit" on entry)
thebigfileofdebugstepping.-.Microsoft.Visual.Studio.Current.Administrator.2021-11-19.23-51-49.mp4
After: like C#, we now have one debug point for
for
on loop entry, and one onin
for each iteration as elements are drawn from the enumeration. Similarly forfor x = 1 to 5 do ...
where the debug points are onfor
andto
.thebigfileofdebugstepping.-.Microsoft.Visual.Studio.Current.Administrator.2021-11-19.23-49-45.mp4
This also