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 optimizations can produce incorrect IL when dealing with byrefs #12438

Closed
GrabYourPitchforks opened this issue Apr 6, 2019 · 5 comments · Fixed by dotnet/coreclr#23984
Closed
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI bug
Milestone

Comments

@GrabYourPitchforks
Copy link
Member

(See also dotnet/coreclr#23783 (comment).)

Repro:

private delegate ref byte MyDelegate(ref byte theRef, IntPtr a, IntPtr b);

static void Main()
{
    DynamicMethod dm = new DynamicMethod("Sample",
        typeof(byte).MakeByRefType(),
        new Type[] { typeof(byte).MakeByRefType(), typeof(IntPtr), typeof(IntPtr) });

    var ilGen = dm.GetILGenerator();
    ilGen.Emit(OpCodes.Ldarg_0);
    ilGen.Emit(OpCodes.Ldc_I4_8);
    ilGen.Emit(OpCodes.Sub);
    ilGen.Emit(OpCodes.Ldarg_1);
    ilGen.Emit(OpCodes.Add);
    ilGen.Emit(OpCodes.Ldc_I4_8);
    ilGen.Emit(OpCodes.Sub);
    ilGen.Emit(OpCodes.Ldarg_2);
    ilGen.Emit(OpCodes.Add);
    ilGen.Emit(OpCodes.Ret);

    var del = (MyDelegate)dm.CreateDelegate(typeof(MyDelegate));
}

Codegen:

DynamicClass.Sample(Byte ByRef, IntPtr, IntPtr):
00007ffc`80220080 4803d1          add     rdx,rcx
00007ffc`80220083 4a8d4402f0      lea     rax,[rdx+r8-10h]
00007ffc`80220088 c3              ret

Here, rcx := theRef&, rdx := a, and r8 := b. The JIT has incorrectly rearranged the addition of a and the subtraction of 8. It's possible that the caller required the operations to be done specifically in that order to avoid a GC hole. The intermediate value ref byte temp = ref theRef + a could result in a temp that points beyond the bounds of the GC-tracked object.

Edit: the following codegen would have been acceptable, since it doesn't result in an intermediate byref.

add rdx, r8
lea rax, [rcx + rdx - 10h]
ret
@AndyAyersMS
Copy link
Member

The jit is likely not sufficiently careful about re-association in address formation.

In normal usage, address computations are always adding positive values, and so it doesn't matter what order the adds are performed.

Fixing this in general without causing regression may not be easy -- for example, we may need to surface implicit assumptions about the non-negativity of some variable addends below guard checks.

I suspect this is a long-standing issue with jitted codegen in general. Will put in 3.0 for now but will likely move this to future once we've looked at it more deeply.

@AndyAyersMS
Copy link
Member

Likely culprit is fgMoveOpsLeft. It has some byref paranoia, but perhaps not enough.

@erozenfeld erozenfeld self-assigned this Apr 9, 2019
@erozenfeld
Copy link
Member

I'll investigate.

@erozenfeld
Copy link
Member

The culprit is this code in fgMorphSmpOpOptional:

https://github.com/dotnet/coreclr/blob/72d49127a0c25e4b931c81e621c2411bfb6633a5/src/jit/morph.cpp#L13991-L14035

I have a change that allows this transformation for byrefs only if the constant is not negative and the other operand is unsigned. With that I get this code for the issue example:

G_M8004_IG02:
       4883C1F8             add      rcx, -8
       4803D1               add      rdx, rcx
       4A8D4402F8           lea      rax, bword ptr [rdx+r8-8]

G_M8004_IG03:
       C3                   ret

@erozenfeld
Copy link
Member

I'll check the diffs to see if this causes many regressions.

erozenfeld referenced this issue in erozenfeld/coreclr Apr 14, 2019
Morph has a transformation ((x + const) + y) => ((x + y) + const).
If x or y is a GC pointer and one of the int operands may be negative,
this may result in a byref temp that points outside of the ref object.
If the code is in a non-interruptible region and a GC happens, the byref
won't be updated.

This change restricts the transformation so that if one of the non-const
operands is a GC pointer, then it's allowed only if the constant operand
is not negative and the other int operand is unsigned and, therefore,
is also not negative.

Fixes #23792.
erozenfeld referenced this issue in erozenfeld/coreclr Apr 14, 2019
Morph has a transformation ((x + const) + y) => ((x + y) + const).
If x or y is a GC pointer and one of the int operands may be negative,
this may result in a byref temp that points outside of the ref object.
If the code is in a non-interruptible region and a GC happens, the byref
won't be updated.

This change restricts the transformation so that if one of the non-const
operands is a GC pointer, then it's allowed only if the constant operand
is not negative and the other int operand is unsigned and, therefore,
is also not negative.

Fixes #23792.
erozenfeld referenced this issue in erozenfeld/coreclr Apr 15, 2019
Morph has transformations
((x + const) + y) => ((x + y) + const)
and
((x + const1) + (y + const2)) => ((x + y) + (const1 + const2))

If x or y is a GC pointer and one of the int operands may be negative,
this may result in a byref temp that points outside of the ref object.
If the code is in a non-interruptible region and a GC happens, the byref
won't be updated.

This change disallows the transformations if one of the non-const
operands is a GC pointer.

Fixes #23792.
erozenfeld referenced this issue in erozenfeld/coreclr Apr 16, 2019
Morph has transformations
((x + const) + y) => ((x + y) + const)
and
((x + const1) + (y + const2)) => ((x + y) + (const1 + const2))

If x or y is a GC pointer and one of the int operands may be negative,
this may result in a byref temp that points outside of the ref object.
If the code is in a non-interruptible region and a GC happens, the byref
won't be updated.

This change disallows the transformations if one of the non-const
operands is a GC pointer.

Fixes #23792.
erozenfeld referenced this issue in dotnet/coreclr Apr 17, 2019
Morph has transformations
((x + const) + y) => ((x + y) + const)
and
((x + const1) + (y + const2)) => ((x + y) + (const1 + const2))

If x or y is a GC pointer and one of the int operands may be negative,
this may result in a byref temp that points outside of the ref object.
If the code is in a non-interruptible region and a GC happens, the byref
won't be updated.

This change disallows the transformations if one of the non-const
operands is a GC pointer.

Fixes #23792.
@msftgits msftgits transferred this issue from dotnet/coreclr Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 13, 2020
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.

4 participants