Skip to content
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

JIT: Loop cloning when the method has GDV skips necessary validity checks #95315

Closed
jakobbotsch opened this issue Nov 28, 2023 · 3 comments · Fixed by #97122
Closed

JIT: Loop cloning when the method has GDV skips necessary validity checks #95315

jakobbotsch opened this issue Nov 28, 2023 · 3 comments · Fixed by #97122
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Milestone

Comments

@jakobbotsch
Copy link
Member

The following program either hits AV or prints memory from a random heap location. We get rid of the bounds check in the loop in Bar.

using System;
using System.Runtime.CompilerServices;
using System.Threading;

public class Program
{
    public static void Main()
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 200; j++)
            {
                try
                {
                    Bar(new int[10], new C());
                }
                catch (IndexOutOfRangeException)
                {
                }
            }

            Thread.Sleep(500);
        }

        Bar(new int[1], new C());
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Bar(int[] arr, I iface)
    {
        if (arr == null)
        {
            return;
        }

        iface.Foo();

        int i = 10000000;
        do
        {
            Console.WriteLine(arr[i]);
            i++;
        } while (i < arr.Length);
    }
}

internal interface I
{
    void Foo();
}

internal class C : I
{
    public void Foo()
    {
    }
}

The problem is that we skip the following check when we have GDVs in the method, but then still end up optimizing bounds checks. That's only legal when this check passes.

if (!loop.lpTestTree->OperIsCompare() || !(loop.lpTestTree->gtFlags & GTF_RELOP_ZTT))

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Nov 28, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Nov 28, 2023
@ghost
Copy link

ghost commented Nov 28, 2023

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

The following program either hits AV or prints memory from a random heap location. We get rid of the bounds check in the loop in Bar.

using System;
using System.Runtime.CompilerServices;
using System.Threading;

public class Program
{
    public static void Main()
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 200; j++)
            {
                try
                {
                    Bar(new int[10], new C());
                }
                catch (IndexOutOfRangeException)
                {
                }
            }

            Thread.Sleep(500);
        }

        Bar(new int[1], new C());
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Bar(int[] arr, I iface)
    {
        if (arr == null)
        {
            return;
        }

        iface.Foo();

        int i = 10000000;
        do
        {
            Console.WriteLine(arr[i]);
            i++;
        } while (i < arr.Length);
    }
}

internal interface I
{
    void Foo();
}

internal class C : I
{
    public void Foo()
    {
    }
}

The problem is that we skip the following check when we have GDVs in the method, but then still end up optimizing bounds checks. That's only legal when this check passes.

if (!loop.lpTestTree->OperIsCompare() || !(loop.lpTestTree->gtFlags & GTF_RELOP_ZTT))

Author: jakobbotsch
Assignees: -
Labels:

area-CodeGen-coreclr

Milestone: -

@jakobbotsch jakobbotsch removed the untriaged New issue has not been triaged by the area owner label Nov 28, 2023
@jakobbotsch jakobbotsch added this to the 9.0.0 milestone Nov 28, 2023
@jakobbotsch
Copy link
Member Author

I'm not sure why we leave this particular validity check up to loop cloning instead of rejecting the induction analysis during loop finding. The results of the induction analysis do not seem useful unless we know the full loop body is protected by the loop test.

@AndyAyersMS
Copy link
Member

Probably the case that when I added GDV driven cloning and generalized the cloning conditions I didn't look carefully enough at the hot loop optimizations, so for a pure GDV clone, they can turn on when they shouldn't.

@jakobbotsch jakobbotsch self-assigned this Jan 7, 2024
jakobbotsch added a commit to jakobbotsch/runtime that referenced this issue Jan 17, 2024
Move the validation of the base case of the IV analysis into
`FlowGraphNaturalLoop::AnalyzeIteration`. Previously the validation for
the base case was left up to the users:

- Loop cloning tried to accomplish this by checking whether loop
  inversion had run, but this is not sufficient (dotnet#96623) as nothing
  guarantees the loop is dominated by the inverted test. Loop cloning
  also skipped the check entirely for GDV, leading to dotnet#95315.

- Unrolling completely neglected to check the condition leading to
  dotnet#96591. Furthermore, unrolling made implicit assumptions that any
  `BBJ_COND` init block was an inverted test and downright removed the
  condition without any checks. This also led to another issue dotnet#97040
  where unrolling could uncover new natural loops that had not been
  canonicalized.

This change makes `FlowGraphNaturalLoop::AnalyzeIteration` try to prove
that the loop invariant it returns is true in the base case as well as
being maintained by the loop. If it cannot prove this then it fails.
This fixes all the issues, but sadly uncovers that we were doing a lot
of cloning in OSR methods without actually proving legality. We no
longer clone in these cases, but we can look into what to do about them
separately.

Fix dotnet#95315
Fix dotnet#96591
Fix dotnet#96623
Fix dotnet#97040
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jan 17, 2024
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jan 18, 2024
tmds pushed a commit to tmds/runtime that referenced this issue Jan 23, 2024
…#97122)

Move the validation of the base case of the IV analysis into
`FlowGraphNaturalLoop::AnalyzeIteration`. Previously the validation for
the base case was left up to the users:

- Loop cloning tried to accomplish this by checking whether loop
  inversion had run, but this is not sufficient (dotnet#96623) as nothing
  guarantees the loop is dominated by the inverted test. Loop cloning
  also skipped the check entirely for GDV, leading to dotnet#95315.

- Unrolling completely neglected to check the condition leading to
  dotnet#96591. Furthermore, unrolling made implicit assumptions that any
  `BBJ_COND` init block was an inverted test and downright removed the
  condition without any checks. This also led to another issue dotnet#97040
  where unrolling could uncover new natural loops that had not been
  canonicalized.

This change makes `FlowGraphNaturalLoop::AnalyzeIteration` try to prove
that the loop invariant it returns is true in the base case as well as
being maintained by the loop. If it cannot prove this then it fails.
This fixes all the issues, but sadly uncovers that we were doing a lot
of cloning in OSR methods without actually proving legality. We no
longer clone in these cases, but we can look into what to do about them
separately.

Fix dotnet#95315
Fix dotnet#96591
Fix dotnet#96623
Fix dotnet#97040
@github-actions github-actions bot locked and limited conversation to collaborators Feb 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants