Skip to content

Commit 9bb6b56

Browse files
authored
Emit debug points at a stack-empty position (#19877)
1 parent bc8a51b commit 9bb6b56

35 files changed

Lines changed: 8192 additions & 6130 deletions

File tree

docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
* Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `<ProduceReferenceAssembly>true</ProduceReferenceAssembly>` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801))
7474
* Parser: recover on unfinished if and binary expressions
7575
([PR #19724](https://github.com/dotnet/fsharp/pull/19724))
76+
* Emit debug points at a stack-empty position ([PR #19877](https://github.com/dotnet/fsharp/pull/19877))
7677
* Fix spurious XmlDoc warnings (unknown parameter / no documentation for parameter) under `--warnon:3390` when a get/set property documents the full parameter set across both accessors. ([Issue #13684](https://github.com/dotnet/fsharp/issues/13684), [PR #19884](https://github.com/dotnet/fsharp/pull/19884))
7778

7879
### Added

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2588,6 +2588,10 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
25882588

25892589
let mutable hasStackAllocatedLocals = false
25902590

2591+
// An uninitialized reference-type 'this', pending for a chained base/self '.ctor', must not be
2592+
// spilled for a debug point: that produces unverifiable IL.
2593+
let mutable uninitializedThisOnStackCount = 0
2594+
25912595
let codeLabelToPC: Dictionary<ILCodeLabel, int> = Dictionary<_, _>(10)
25922596

25932597
let codeLabelToCodeLabel: Dictionary<ILCodeLabel, ILCodeLabel> =
@@ -2629,6 +2633,12 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
26292633

26302634
member _.GetCurrentStack() = stack
26312635

2636+
member _.StartUninitializedThisOnStack() =
2637+
uninitializedThisOnStackCount <- uninitializedThisOnStackCount + 1
2638+
2639+
member _.EndUninitializedThisOnStack() =
2640+
uninitializedThisOnStackCount <- uninitializedThisOnStackCount - 1
2641+
26322642
member _.AssertEmptyStack() =
26332643
if not (isNil stack) then
26342644
let msg =
@@ -2676,6 +2686,21 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
26762686
member cgbuf.EmitDebugPoint(m: range) =
26772687
if mgbuf.cenv.options.generateDebugSymbols then
26782688

2689+
// A debug point must be at an empty stack position for the debugger to bind a breakpoint there,
2690+
// so spill anything still pending (e.g. a call argument) to temporaries and reload it afterwards.
2691+
// An uninitialized reference-type 'this' (pending for a chained '.ctor') can't be spilled, so
2692+
// leave the stack as-is in that case.
2693+
let spilled =
2694+
if uninitializedThisOnStackCount > 0 then
2695+
[]
2696+
else
2697+
[
2698+
for ty in stack ->
2699+
let idx = cgbuf.AllocLocal([], ty, false, true)
2700+
cgbuf.EmitInstr(pop 1, Push0, mkStloc (uint16 idx))
2701+
idx, ty
2702+
]
2703+
26792704
let attr = GenILSourceMarker g m
26802705
let i = I_seqpoint attr
26812706
hasDebugPoints <- true
@@ -2696,6 +2721,9 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
26962721

26972722
anyDocument <- Some attr.Document
26982723

2724+
for idx, ty in List.rev spilled do
2725+
cgbuf.EmitInstr(pop 0, Push [ ty ], mkLdloc (uint16 idx))
2726+
26992727
// Emit FeeFee breakpoints for hidden code, see https://blogs.msdn.microsoft.com/jmstall/2005/06/19/line-hidden-and-0xfeefee-sequence-points/
27002728
member cgbuf.EmitStartOfHiddenCode() =
27012729
if mgbuf.cenv.options.generateDebugSymbols then
@@ -4505,6 +4533,7 @@ and GenApp (cenv: cenv) cgbuf eenv (f, fty, tyargs, curriedArgs, m) sequel =
45054533
List.splitAt numEnclILTypeArgs ilTyArgs
45064534

45074535
let boxity = mspec.DeclaringType.Boxity
4536+
let valu = boxity = AsValue
45084537
let mspec = mkILMethSpec (mspec.MethodRef, boxity, ilEnclArgTys, ilMethArgTys)
45094538

45104539
// "Unit" return types on static methods become "void"
@@ -4560,8 +4589,20 @@ and GenApp (cenv: cenv) cgbuf eenv (f, fty, tyargs, curriedArgs, m) sequel =
45604589
I_call(isTailCall, mspec, None)
45614590

45624591
// ok, now we're ready to generate
4592+
// For a value type the constructor 'this' is a managed pointer, so track it as a byref.
4593+
let thisTy =
4594+
if valu then
4595+
ILType.Byref mspec.DeclaringType
4596+
else
4597+
mspec.DeclaringType
4598+
45634599
if isSuperInit || isSelfInit then
4564-
CG.EmitInstr cgbuf (pop 0) (Push [ mspec.DeclaringType ]) mkLdarg0
4600+
CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
4601+
4602+
let pendingUninitializedThis = (isSuperInit || isSelfInit) && not valu
4603+
4604+
if pendingUninitializedThis then
4605+
cgbuf.StartUninitializedThisOnStack()
45654606

45664607
if not cenv.g.generateWitnesses || witnessInfos.IsEmpty then
45674608
() // no witness args
@@ -4602,9 +4643,12 @@ and GenApp (cenv: cenv) cgbuf eenv (f, fty, tyargs, curriedArgs, m) sequel =
46024643

46034644
CG.EmitInstr cgbuf (pop (nargs + (if mspec.CallingConv.IsStatic || newobj then 0 else 1))) pushes callInstr
46044645

4646+
if pendingUninitializedThis then
4647+
cgbuf.EndUninitializedThisOnStack()
4648+
46054649
// For isSuperInit, load the 'this' pointer as the pretend 'result' of the operation. It will be popped again in most cases
46064650
if isSuperInit then
4607-
CG.EmitInstr cgbuf (pop 0) (Push [ mspec.DeclaringType ]) mkLdarg0
4651+
CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
46084652

46094653
// When generating debug code, generate a 'nop' after a 'call' that returns 'void'
46104654
// This is what C# does, as it allows the call location to be maintained correctly in the stack frame
@@ -5629,10 +5673,21 @@ and GenILCall
56295673
(virt || useCallVirt cenv boxity ilMethSpec isBaseCall)
56305674
&& ilMethRef.CallingConv.IsInstance
56315675

5676+
let thisTy =
5677+
if valu then
5678+
ILType.Byref ilMethSpec.DeclaringType
5679+
else
5680+
ilMethSpec.DeclaringType
5681+
56325682
// Load the 'this' pointer to pass to the superclass constructor. This argument is not
56335683
// in the expression tree since it can't be treated like an ordinary value
56345684
if isSuperInit then
5635-
CG.EmitInstr cgbuf (pop 0) (Push [ ilMethSpec.DeclaringType ]) mkLdarg0
5685+
CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
5686+
5687+
let pendingUninitializedThis = isSuperInit && not valu
5688+
5689+
if pendingUninitializedThis then
5690+
cgbuf.StartUninitializedThisOnStack()
56365691

56375692
GenExprs cenv cgbuf eenv argExprs
56385693

@@ -5652,10 +5707,13 @@ and GenILCall
56525707

56535708
CG.EmitInstr cgbuf (pop (argExprs.Length + (if isSuperInit then 1 else 0))) (if isSuperInit then Push0 else Push ilReturnTys) il
56545709

5710+
if pendingUninitializedThis then
5711+
cgbuf.EndUninitializedThisOnStack()
5712+
56555713
// Load the 'this' pointer as the pretend 'result' of the isSuperInit operation.
56565714
// It will be immediately popped in most cases, but may also be used as the target of some "property set" operations.
56575715
if isSuperInit then
5658-
CG.EmitInstr cgbuf (pop 0) (Push [ ilMethSpec.DeclaringType ]) mkLdarg0
5716+
CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
56595717

56605718
CommitCallSequel cenv eenv m eenv.cloc cgbuf mustGenerateUnitAfterCall sequel
56615719

tests/AheadOfTime/Trimming/check.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len, $callerLineNumber) {
6363
$allErrors = @()
6464

6565
# Check net9.0 trimmed assemblies
66-
$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 311296 -callerLineNumber 66
66+
$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 311808 -callerLineNumber 66
6767

6868
# Check net9.0 trimmed assemblies with static linked FSharpCore
69-
$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9168384 -callerLineNumber 69
69+
$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9169408 -callerLineNumber 69
7070

7171
# Check net9.0 trimmed assemblies with F# metadata resources removed
7272
$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7609344 -callerLineNumber 72

0 commit comments

Comments
 (0)