Skip to content

Release-only wrong result in integer for loop: local bound inside loop range expression clobbers a local slot #20732

Open

Description

@rspeele

Release-only wrong result in integer for loop: local bound inside loop range expression clobbers a local slot

Repro steps

//// repro.fsx ////
// dotnet fsi --optimize- repro.fsx   prints: 100
// dotnet fsi --optimize+ repro.fsx   prints: 50

let f k =
    k + (for _ in max 0 (k - 50) .. 0 do stdout.Write ""
         0)

printfn "%d" (f 100)

Expected behavior

The behavior without optimization is correct. f k is equivalent to k+0, so f 100 should be 100.

Actual behavior

With optimization on, the generated IL spills k off the stack into a local variable slot, but then when evaluating the loop start expression, max needs a local to store k - 50.

GenIntegerForLoop puts that local in the same slot. That clobbers k.

In my first example the start expression is the culprit. But the end expression has the same problem when the optimizer rewrites a loop like for i in 0 .. arr.Length - 1 C#-style. The local can take the loop variable's own slot, and the loop never terminates. Example:

//// repro2.fsx ////
// dotnet fsi --optimize- repro2.fsx   prints: 0 1 2
// dotnet fsi --optimize+ repro2.fsx   prints: 0 0 0 0 0 ... forever

let f (xs: int[]) n =
                  // this could be any side-effect-free expr that binds a local
    for i in 0 .. (if max 0 (n - 1) > 0 then xs else xs).Length - 1 do
        printf "%d " i

f [| 1; 2; 3 |] 1

Known workarounds

Bind the loop's start and/or end to locals with let. Use for x in startVar .. endVar do instead of putting complex expressions directly inside the in _ .. _ part of the loop.

Related information

Windows 10, SDK 10.0.401, and reproduced on main at 3917a24

This bug repros going back at least to SDK 6.0.428. But going from 10.0.202 to 10.0.401, it became more visible as it can affect some usages of Array.init as well. This is how I ran into it.

//// repro3.fsx ////
// dotnet fsi --optimize- repro3.fsx   prints: [|0; 1; 2|]
// dotnet fsi --optimize+ repro3.fsx   prints: [|2; 1; 0|]

let xs =
    Array.init 3 (fun k ->
        for _ in max 0 (2 - k) .. 0 do stdout.Write ""
        k)

printfn "%A" xs

I will follow up with a small PR to fix this.

Possibly related: #20564 (different symptom, also stack saving around a branch)

Activity

  1. added theissue type on Oct 8, 2026
  2. added this to the Backlog milestone on Oct 8, 2026
  3. added a commit that references this issue on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions