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

Loop cloning misses definitions for fields of promoted structs #61040

Closed
SingleAccretion opened this issue Oct 30, 2021 · 8 comments · Fixed by #70126
Closed

Loop cloning misses definitions for fields of promoted structs #61040

SingleAccretion opened this issue Oct 30, 2021 · 8 comments · Fixed by #70126
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI bug
Milestone

Comments

@SingleAccretion
Copy link
Contributor

Reproduction:

using System.Runtime.CompilerServices;

Problem(default);

[MethodImpl(MethodImplOptions.NoInlining)]
static void JitUse<T>(T arg) { }

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static void Problem(ArrayWrapper a)
{
    a = GetArrayLong();

    JitUse(a);
    JitUse(a);

    for (int i = 0; i < 10000; i++)
    {
        a = GetArray();
        JitUse(a.Array[i]);
    }
}

[MethodImpl(MethodImplOptions.NoInlining)]
static ArrayWrapper GetArray() => new() { Array = new int[0] };

[MethodImpl(MethodImplOptions.NoInlining)]
static ArrayWrapper GetArrayLong() => new() { Array = new int[10000] };

struct ArrayWrapper
{
    public int[] Array;
}

We expect to get an IndexOutOfRangeException here, but instead get an AV.

The cause is that loop cloning thinks that the promoted struct's field is invariant in the loop and deletes the bounds check. The reason it thinks it is invariant is because this is a special case in block morphing, where we leave an independently promoted struct alone, and it looks like optIsVarAssgCB does not account for this. I thus conjecture this will also reproduce for the multi-reg assignment form.

Found while experimenting with morphing of the "one field" case into ASG(LCL_VAR(promoted field), BITCAST(field type(CALL))).

@dotnet/jit-contrib

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

ghost commented Oct 30, 2021

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

Issue Details

Reproduction:

using System.Runtime.CompilerServices;

Problem(default);

[MethodImpl(MethodImplOptions.NoInlining)]
static void JitUse<T>(T arg) { }

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static void Problem(ArrayWrapper a)
{
    a = GetArrayLong();

    JitUse(a);
    JitUse(a);

    for (int i = 0; i < 10000; i++)
    {
        a = GetArray();
        JitUse(a.Array[i]);
    }
}

[MethodImpl(MethodImplOptions.NoInlining)]
static ArrayWrapper GetArray() => new() { Array = new int[0] };

[MethodImpl(MethodImplOptions.NoInlining)]
static ArrayWrapper GetArrayLong() => new() { Array = new int[10000] };

struct ArrayWrapper
{
    public int[] Array;
}

We expect to get an IndexOutOfRangeException here, but instead get an AV.

The cause is that loop cloning thinks that the promoted struct's field is invariant in the loop and deletes the bounds check. The reason it thinks it is invariant is because this is a special case in block morphing, where we leave an independently promoted struct alone, and it looks like optIsVarAssgCB does not account for this. I thus conjecture this will also reproduce for the multi-reg assignment form.

Found while experimenting with morphing of the "one field" case into ASG(LCL_VAR(promoted field), BITCAST(field type(CALL))).

@dotnet/jit-contrib

Author: SingleAccretion
Assignees: -
Labels:

area-CodeGen-coreclr, untriaged

Milestone: -

@EgorBo
Copy link
Member

EgorBo commented Oct 30, 2021

@SingleAccretion is this .NET 6.0 specific?

@SingleAccretion
Copy link
Contributor Author

SingleAccretion commented Oct 30, 2021

I believe not, this sample on sharplab produces similarly incorrect codegen.

Edit: indeed, targeting net5.0, I still see the AV. However, targeting netcoreapp3.1, the reproduction is no more.

@BruceForstall BruceForstall self-assigned this Oct 30, 2021
@SingleAccretion
Copy link
Contributor Author

SingleAccretion commented Oct 30, 2021

It seems that this problem exists for any case where morph leaves a copy block for promoted destinations. E. g. here's an example for "a custom layout with holes":

using System.Runtime.CompilerServices;

Problem(new() { Index = 0 }, new() { Index = 10000 }, new int[10]);

[MethodImpl(MethodImplOptions.NoInlining)]
public static void JitUse<T>(T arg) { }

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
private static void Problem(StructWithHoles a, StructWithHoles b, int[] d)
{
    var a1 = a;
    var b1 = b;

    for (a1.Index = 0; a1.Index < 10; a1.Index = a1.Index + 1)
    {
        a1 = b1;
        JitUse(d[a1.Index]);
    }
}

[StructLayout(LayoutKind.Explicit)]
struct StructWithHoles
{
    [FieldOffset(0)]
    public int Index;
    [FieldOffset(5)]
    public byte B;
    [FieldOffset(8)]
    public int C;
}
Fatal error. System.AccessViolationException: Attempted to read or write protected memory.

Or another "call" case:

using System.Runtime.CompilerServices;

Problem(new() { Index = 0 }, new int[10]);

[MethodImpl(MethodImplOptions.NoInlining)]
public static void JitUse<T>(T arg) { }

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
private static void Problem(StructWithIndex a, int[] d)
{
    var a1 = a;

    for (a1.Index = 0; a1.Index < 10; a1.Index = a1.Index + 1)
    {
        a1 = GetStructWithIndex();
        JitUse(d[a1.Index]);
    }
}

[MethodImpl(MethodImplOptions.NoInlining)]
static StructWithIndex GetStructWithIndex() => new() { Index = 10000 };

struct StructWithIndex
{
    public int Index;
    public int Value;
}

@AndyAyersMS
Copy link
Member

@BruceForstall have you looked into this yet? If not, maybe I can take it over.

@AndyAyersMS
Copy link
Member

FYI the second and third cases above don't fail with recent main.

I assume they could still be made to fail with suitable tweaks.

@BruceForstall
Copy link
Member

@BruceForstall have you looked into this yet? If not, maybe I can take it over.

I haven't. Feel free to take it.

@AndyAyersMS
Copy link
Member

Here's a similar case we get wrong:

using System.Runtime.CompilerServices;

return Problem();

[MethodImpl(MethodImplOptions.NoInlining)]
static void JitUse<T>(T arg) { }

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static int Problem()
{
    int[] a = GetArray();
    int[] b = a;

    JitUse(a);
    JitUse(b);

    int r = 0;

    for (int i = 0; i < a.Length; i++)
    {
        a = GetArrayLong();
        r += b[i];
    }

    return r;
}

[MethodImpl(MethodImplOptions.NoInlining)]
static int[] GetArray() => new int[] { 1, 2, 3, 4, 90 };

[MethodImpl(MethodImplOptions.NoInlining)]
static int[] GetArrayLong() => new int[10000];

We assume if the iterator var is compared to an array length, that array length is a loop invariant. But it might not be, and we can end up walking off the end of an array with no check.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this issue Jun 2, 2022
Fix some cases where the JIT was not sufficiently careful in verifing that
operands in a loop were suitably invariant.

Closes dotnet#61040.
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jun 2, 2022
AndyAyersMS added a commit that referenced this issue Jun 3, 2022
Fix some cases where the JIT was not sufficiently careful in verifying that
operands in a loop were invariant.

Closes #61040.
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jun 3, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Jul 3, 2022
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 bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants