Prevent tailcalls when locals are pinned or address-taken; add regression coverage
Tail calls were still emitted when pinning locals (e.g., fixed &value or &&value), leading to corrupted results at runtime.
-
Codegen safety: Track address-taken locals in
IlxGenand suppress tailcall emission when any local is pinned or its address is taken. -
Regression tests: Added TailCall 08/09 component tests covering pinned locals and the
&&operator to ensure correct output.
Example:
[<MethodImpl(MethodImplOptions.NoInlining)>]
let foo() =
let mutable value = 42
use ptr = fixed &value // or: bar &&value
bar ptr // should print 42 without tailcall
Original prompt
This section details on the original issue you should resolve
<issue_title>Tail calls with pinned locals</issue_title> <issue_description>Opening another issue, since the original is closed and I haven't gotten a response there.
The original problem with pinned arrays seems to be fixed, the tail call is gone when using the newest SDK. However, there are still problems when pinning a local variable:
Repro steps
open Microsoft.FSharp.NativeInterop open System.Runtime.CompilerServices module Program = [<MethodImpl(MethodImplOptions.NoInlining)>] let bar (pValue: nativeptr<int>) : unit = let value = NativePtr.read pValue printfn "value = %A" value [<MethodImpl(MethodImplOptions.NoInlining)>] let foo() = let mutable value = 42 use ptr = fixed &value bar ptr [<EntryPoint>] let main argv = foo() 0The same also happens when using the taking the address directly with the
&&operator:[<MethodImpl(MethodImplOptions.NoInlining)>] let foo() = let mutable value = 42 bar &&valueExpected behavior
Should print 42.
Actual behavior
Tail call gets emitted and prints garbage:
IL_0003: ldloca.s 'value' IL_0005: stloc.1 // V_1 IL_0006: ldloca.s 'value' IL_0008: conv.i IL_0009: tail. IL_000b: call void Program/Program::bar(int32*) IL_0010: retKnown workarounds
Wrap the
barcall in atryfinallyblock.Related information
- .NET SDK 10.0.101</issue_description>
<agent_instructions>Locate the changes done at https://github.com/dotnet/fsharp/pull/18893 . Add the newly reported test cases to it.
Execute them (not just type check, but real tests that do
compileas wellrunin the test framework).Include the test case with the
&&operator - I do not see it as mandatory change though, this operator should not be used in my opinion.Prepare changes needed to Ilxgen for preventing a tail call emission. Keep a TODO list and list all attempts you tried, make sure to push that TODO list as part of you work.
If you encounter any behavior, write down HYPOTHESIS.md with what you think is happening - there can be more of those. For each, add necessary instrumentation in the code and track verified/rejected in the HYPOTHESIS.md file.</agent_instructions>
Comments on the Issue (you are @copilot in this section)
Custom agent used: F# agent Generic agent for F# coding following the coding guidelines of F# from MsLearn
- Fixes dotnet/fsharp#19147
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.
/run test-baseline EmittedIL
:white_check_mark: No release notes required
🔧 CLI Command Report
-
Command:
/run test-baseline - Outcome: success
✅ Patch applied: - Files changed: 2 - Lines changed: 240
/run test-baseline EmittedIL
🔧 CLI Command Report
-
Command:
/run test-baseline - Outcome: success
✅ Patch applied: - Files changed: 2 - Lines changed: 64