From 00f0fe652dfceb7d05adb57848625dd73116f28f Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 19 Aug 2026 15:12:19 +0200 Subject: [PATCH 1/2] Test: debug-inline capture of a local function from a nested-closure callsite Regression coverage for #20297. The reduced repro keeps the reported shape - a captured local function, a callsite inside a nested closure, and '>>' composition - and crashes with AccessViolationException on release/10.0.4xx, where __debug@N is emitted as a two-argument static method that does 'ldarg.0; ldfld tee' against the string parameter. It passes once #20089 is present. SRTP 30-38 all call the inline function directly from the enclosing method, so none of them cover a callsite inside a closure. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../EmittedIL/DebugInlineAsCall.fs | 21 +++ ...function, callsite in a nested closure.bsl | 166 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs index 9dce424c4be..e065cc8f14f 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs @@ -1252,6 +1252,27 @@ let main _ = |> compileAndRun |> verifySequencePoints + // https://github.com/dotnet/fsharp/issues/20297 + [] + let ``SRTP 39 - Captured local function, callsite in a nested closure`` () = + FSharp """ +let f () = + let tee g (x: int) = g x; x + let addName (key: string) (v: string) = tee (fun x -> printfn "%s=%s" key v) + let inline addEnum (key: string) value = tee (fun x -> printfn "%s=%d" key (x + int value)) + let pipeline v = addName "a" "n" >> addEnum "b" v + pipeline 1uy 41 + +[] +let main _ = + if f () = 41 then 0 else 1 +""" + |> withDebug + |> withNoOptimize + |> asExe + |> compileAndRun + |> verifySequencePoints + [] let ``Member 01 - Non-generic`` () = FSharp """ diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl new file mode 100644 index 00000000000..9036352c225 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl @@ -0,0 +1,166 @@ +let f () = + let tee g (x: int) = g x; x + let addName (key: string) (v: string) = tee (fun x -> printfn "%s=%s" key v) + let inline addEnum (key: string) value = tee (fun x -> printfn "%s=%d" key (x + int value)) + let pipeline v = addName "a" "n" >> addEnum "b" v + pipeline 1uy 41 + +[] +let main _ = + if f () = 41 then 0 else 1 +-------------------------------------------------------------------------------- + +Test::f + + IL_0000: ldsfld tee@3::@_instance + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: newobj addName@4::.ctor + IL_000c: stloc.1 + IL_000d: ldloc.0 + IL_000e: newobj addEnum@5::.ctor + IL_0013: stloc.2 + IL_0014: ldloc.0 + IL_0015: ldloc.1 + IL_0016: newobj pipeline@6::.ctor + IL_001b: stloc.3 + + (7,5-7,20) pipeline 1uy 41 + IL_001c: ldloc.3 + IL_001d: ldc.i4.1 + IL_001e: ldc.i4.s 41 + IL_0020: tail. + IL_0022: call InvokeFast + IL_0027: ret + +Test::main + (11,5-11,22) if f () = 41 then + IL_0000: call Test::f + IL_0005: ldc.i4.s 41 + IL_0007: bne.un.s IL_000b + + (11,23-11,24) 0 + IL_0009: ldc.i4.0 + IL_000a: ret + + (11,30-11,31) 1 + IL_000b: ldc.i4.1 + IL_000c: ret + +Test::__debug@6 + (5,46-5,96) tee (fun x -> printfn "%s=%d" key (x + int value)) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldarg.2 + IL_0003: newobj pipeline@5-1::.ctor + IL_0008: tail. + IL_000a: callvirt Invoke + IL_000f: ret + +tee@3::Invoke + (3,26-3,29) g x + IL_0000: ldarg.1 + IL_0001: ldarg.2 + IL_0002: callvirt Invoke + IL_0007: pop + + (3,31-3,32) x + IL_0008: ldarg.2 + IL_0009: ret + +addName@4::Invoke + (4,45-4,81) tee (fun x -> printfn "%s=%s" key v) + IL_0000: ldarg.0 + IL_0001: ldfld addName@4::tee + IL_0006: ldarg.1 + IL_0007: ldarg.2 + IL_0008: newobj addName@4-1::.ctor + IL_000d: tail. + IL_000f: callvirt Invoke + IL_0014: ret + +addName@4-1::Invoke + (4,59-4,80) printfn "%s=%s" key v + IL_0000: ldstr "%s=%s" + IL_0005: newobj .ctor + IL_000a: call ExtraTopLevelOperators::PrintFormatLine + IL_000f: stloc.0 + IL_0010: ldloc.0 + IL_0011: newobj addName@4-2::.ctor + IL_0016: ldarg.0 + IL_0017: ldfld addName@4-1::key + IL_001c: ldarg.0 + IL_001d: ldfld addName@4-1::v + IL_0022: tail. + IL_0024: call InvokeFast + IL_0029: ret + +addEnum@5-1::Invoke + (5,46-5,96) tee (fun x -> printfn "%s=%d" key (x + int value)) + IL_0000: ldarg.0 + IL_0001: ldfld tee + IL_0006: ldarg.1 + IL_0007: ldarg.2 + IL_0008: newobj .ctor + IL_000d: tail. + IL_000f: callvirt Invoke + IL_0014: ret + +addEnum@5-2::Invoke + (5,60-5,95) printfn "%s=%d" key (x + int value) + IL_0000: ldstr "%s=%d" + IL_0005: newobj .ctor + IL_000a: call ExtraTopLevelOperators::PrintFormatLine + IL_000f: stloc.0 + IL_0010: ldloc.0 + IL_0011: newobj addEnum@5-3::.ctor + IL_0016: ldarg.0 + IL_0017: ldfld key + IL_001c: ldarg.1 + IL_001d: ldarg.0 + IL_001e: ldfld value + IL_0023: stloc.1 + IL_0024: ldloc.1 + IL_0025: call LanguagePrimitives::ExplicitDynamic + IL_002a: add + IL_002b: tail. + IL_002d: call InvokeFast + IL_0032: ret + +pipeline@5-1::Invoke + (5,60-5,95) printfn "%s=%d" key (x + int value) + IL_0000: ldstr "%s=%d" + IL_0005: newobj .ctor + IL_000a: call ExtraTopLevelOperators::PrintFormatLine + IL_000f: stloc.0 + IL_0010: ldloc.0 + IL_0011: newobj pipeline@5-2::.ctor + IL_0016: ldarg.0 + IL_0017: ldfld pipeline@5-1::key + IL_001c: ldarg.1 + IL_001d: ldarg.0 + IL_001e: ldfld pipeline@5-1::value + IL_0023: conv.i4 + IL_0024: add + IL_0025: tail. + IL_0027: call InvokeFast + IL_002c: ret + +pipeline@6::Invoke + (6,22-6,54) addName "a" "n" >> addEnum "b" v + IL_0000: ldarg.0 + IL_0001: ldfld pipeline@6::addName + IL_0006: ldstr "a" + IL_000b: ldstr "n" + IL_0010: call InvokeFast + IL_0015: stloc.0 + IL_0016: ldarg.0 + IL_0017: ldfld pipeline@6::tee + IL_001c: ldstr "b" + IL_0021: ldarg.1 + IL_0022: call Test::__debug@6 + IL_0027: stloc.1 + IL_0028: ldloc.0 + IL_0029: ldloc.1 + IL_002a: newobj pipeline@6-4::.ctor + IL_002f: ret From ac6ae8285ece7697775cb1d6d16f16f9a7745988 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 20 Aug 2026 13:45:48 +0200 Subject: [PATCH 2/2] Minimize SRTP 39 debug-inline capture repro test Address review feedback: strip printfn and addName noise from the repro so the emitted-IL baseline is easier to follow. The minimized source keeps all essential ingredients (inline function whose body is a lambda capturing an outer local 'tee', used first-class inside a nested closure via >>) and the SRTP 'int value'/ExplicitDynamic aspect, so __debug@N still reaches the captured local through a closure field. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../EmittedIL/DebugInlineAsCall.fs | 5 +- ...function, callsite in a nested closure.bsl | 173 ++++++------------ 2 files changed, 59 insertions(+), 119 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs index e065cc8f14f..976b1c5f68f 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall.fs @@ -1258,9 +1258,8 @@ let main _ = FSharp """ let f () = let tee g (x: int) = g x; x - let addName (key: string) (v: string) = tee (fun x -> printfn "%s=%s" key v) - let inline addEnum (key: string) value = tee (fun x -> printfn "%s=%d" key (x + int value)) - let pipeline v = addName "a" "n" >> addEnum "b" v + let inline addEnum value = tee (fun x -> ignore (int value)) + let pipeline v = id >> addEnum v pipeline 1uy 41 [] diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl index 9036352c225..e69c8ab8b3d 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 39 - Captured local function, callsite in a nested closure.bsl @@ -1,8 +1,7 @@ let f () = let tee g (x: int) = g x; x - let addName (key: string) (v: string) = tee (fun x -> printfn "%s=%s" key v) - let inline addEnum (key: string) value = tee (fun x -> printfn "%s=%d" key (x + int value)) - let pipeline v = addName "a" "n" >> addEnum "b" v + let inline addEnum value = tee (fun x -> ignore (int value)) + let pipeline v = id >> addEnum v pipeline 1uy 41 [] @@ -15,47 +14,42 @@ Test::f IL_0000: ldsfld tee@3::@_instance IL_0005: stloc.0 IL_0006: ldloc.0 - IL_0007: newobj addName@4::.ctor + IL_0007: newobj addEnum@4::.ctor IL_000c: stloc.1 IL_000d: ldloc.0 - IL_000e: newobj addEnum@5::.ctor + IL_000e: newobj pipeline@5::.ctor IL_0013: stloc.2 - IL_0014: ldloc.0 - IL_0015: ldloc.1 - IL_0016: newobj pipeline@6::.ctor - IL_001b: stloc.3 - (7,5-7,20) pipeline 1uy 41 - IL_001c: ldloc.3 - IL_001d: ldc.i4.1 - IL_001e: ldc.i4.s 41 - IL_0020: tail. - IL_0022: call InvokeFast - IL_0027: ret + (6,5-6,20) pipeline 1uy 41 + IL_0014: ldloc.2 + IL_0015: ldc.i4.1 + IL_0016: ldc.i4.s 41 + IL_0018: tail. + IL_001a: call InvokeFast + IL_001f: ret Test::main - (11,5-11,22) if f () = 41 then + (10,5-10,22) if f () = 41 then IL_0000: call Test::f IL_0005: ldc.i4.s 41 IL_0007: bne.un.s IL_000b - (11,23-11,24) 0 + (10,23-10,24) 0 IL_0009: ldc.i4.0 IL_000a: ret - (11,30-11,31) 1 + (10,30-10,31) 1 IL_000b: ldc.i4.1 IL_000c: ret -Test::__debug@6 - (5,46-5,96) tee (fun x -> printfn "%s=%d" key (x + int value)) +Test::__debug@5 + (4,32-4,65) tee (fun x -> ignore (int value)) IL_0000: ldarg.0 IL_0001: ldarg.1 - IL_0002: ldarg.2 - IL_0003: newobj pipeline@5-1::.ctor - IL_0008: tail. - IL_000a: callvirt Invoke - IL_000f: ret + IL_0002: newobj pipeline@4-2::.ctor + IL_0007: tail. + IL_0009: callvirt Invoke + IL_000e: ret tee@3::Invoke (3,26-3,29) g x @@ -68,99 +62,46 @@ tee@3::Invoke IL_0008: ldarg.2 IL_0009: ret -addName@4::Invoke - (4,45-4,81) tee (fun x -> printfn "%s=%s" key v) - IL_0000: ldarg.0 - IL_0001: ldfld addName@4::tee - IL_0006: ldarg.1 - IL_0007: ldarg.2 - IL_0008: newobj addName@4-1::.ctor - IL_000d: tail. - IL_000f: callvirt Invoke - IL_0014: ret - -addName@4-1::Invoke - (4,59-4,80) printfn "%s=%s" key v - IL_0000: ldstr "%s=%s" - IL_0005: newobj .ctor - IL_000a: call ExtraTopLevelOperators::PrintFormatLine - IL_000f: stloc.0 - IL_0010: ldloc.0 - IL_0011: newobj addName@4-2::.ctor - IL_0016: ldarg.0 - IL_0017: ldfld addName@4-1::key - IL_001c: ldarg.0 - IL_001d: ldfld addName@4-1::v - IL_0022: tail. - IL_0024: call InvokeFast - IL_0029: ret - -addEnum@5-1::Invoke - (5,46-5,96) tee (fun x -> printfn "%s=%d" key (x + int value)) +addEnum@4-1::Invoke + (4,32-4,65) tee (fun x -> ignore (int value)) IL_0000: ldarg.0 IL_0001: ldfld tee IL_0006: ldarg.1 - IL_0007: ldarg.2 - IL_0008: newobj .ctor - IL_000d: tail. - IL_000f: callvirt Invoke - IL_0014: ret - -addEnum@5-2::Invoke - (5,60-5,95) printfn "%s=%d" key (x + int value) - IL_0000: ldstr "%s=%d" - IL_0005: newobj .ctor - IL_000a: call ExtraTopLevelOperators::PrintFormatLine - IL_000f: stloc.0 - IL_0010: ldloc.0 - IL_0011: newobj addEnum@5-3::.ctor - IL_0016: ldarg.0 - IL_0017: ldfld key - IL_001c: ldarg.1 - IL_001d: ldarg.0 - IL_001e: ldfld value - IL_0023: stloc.1 - IL_0024: ldloc.1 - IL_0025: call LanguagePrimitives::ExplicitDynamic - IL_002a: add - IL_002b: tail. - IL_002d: call InvokeFast - IL_0032: ret + IL_0007: newobj .ctor + IL_000c: tail. + IL_000e: callvirt Invoke + IL_0013: ret -pipeline@5-1::Invoke - (5,60-5,95) printfn "%s=%d" key (x + int value) - IL_0000: ldstr "%s=%d" - IL_0005: newobj .ctor - IL_000a: call ExtraTopLevelOperators::PrintFormatLine - IL_000f: stloc.0 - IL_0010: ldloc.0 - IL_0011: newobj pipeline@5-2::.ctor - IL_0016: ldarg.0 - IL_0017: ldfld pipeline@5-1::key - IL_001c: ldarg.1 - IL_001d: ldarg.0 - IL_001e: ldfld pipeline@5-1::value - IL_0023: conv.i4 - IL_0024: add - IL_0025: tail. - IL_0027: call InvokeFast - IL_002c: ret +addEnum@4-2::Invoke + (4,46-4,64) ignore (int value) + IL_0000: ldarg.0 + IL_0001: ldfld value + IL_0006: stloc.1 + IL_0007: ldloc.1 + IL_0008: call LanguagePrimitives::ExplicitDynamic + IL_000d: stloc.0 + IL_000e: ldnull + IL_000f: ret -pipeline@6::Invoke - (6,22-6,54) addName "a" "n" >> addEnum "b" v +pipeline@4-2::Invoke + (4,46-4,64) ignore (int value) IL_0000: ldarg.0 - IL_0001: ldfld pipeline@6::addName - IL_0006: ldstr "a" - IL_000b: ldstr "n" - IL_0010: call InvokeFast - IL_0015: stloc.0 - IL_0016: ldarg.0 - IL_0017: ldfld pipeline@6::tee - IL_001c: ldstr "b" - IL_0021: ldarg.1 - IL_0022: call Test::__debug@6 - IL_0027: stloc.1 - IL_0028: ldloc.0 - IL_0029: ldloc.1 - IL_002a: newobj pipeline@6-4::.ctor - IL_002f: ret + IL_0001: ldfld pipeline@4-2::value + IL_0006: conv.i4 + IL_0007: stloc.0 + IL_0008: ldnull + IL_0009: ret + +pipeline@5::Invoke + (5,22-5,37) id >> addEnum v + IL_0000: ldsfld pipeline@5-1::@_instance + IL_0005: stloc.0 + IL_0006: ldarg.0 + IL_0007: ldfld pipeline@5::tee + IL_000c: ldarg.1 + IL_000d: call Test::__debug@5 + IL_0012: stloc.1 + IL_0013: ldloc.0 + IL_0014: ldloc.1 + IL_0015: newobj pipeline@5-3::.ctor + IL_001a: ret