-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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 incorrectly reorders constrained call 'this' indirections with other arguments #73615
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsSmall example: using InlineIL;
using System;
public class Program
{
public static void Main()
{
Foo(new C("A"));
}
private static void Foo(C arg)
{
IL.Emit.Ldarga(nameof(arg));
IL.Emit.Ldarga(nameof(arg));
IL.Emit.Call(new MethodRef(typeof(Program), nameof(Bar)));
IL.Emit.Constrained<C>();
IL.Emit.Callvirt(new MethodRef(typeof(C), nameof(arg.Baz)));
}
private static int Bar(ref C o)
{
o = new C("B");
return 0;
}
}
public record class C(string Name)
{
public void Baz(int arg)
{
Console.WriteLine(Name);
}
} Expected: B Seen in #73606 (comment)
|
Constrained calls in IL include an implicit dereference that occurs as part of the call. The JIT was adding this dereference on top of the 'this' argument tree, which makes it happen too early (before other arguments are evaluated). This changes the importer to spill when necessary to preserve ordering. For: ```cil .method private hidebysig static void Foo(class Runtime_73615/C arg) cil managed noinlining { // Code size 21 (0x15) .maxstack 2 IL_0000: ldarga.s arg IL_0002: ldarga.s arg IL_0004: call int32 Runtime_73615::Bar(class Runtime_73615/C&) IL_0009: constrained. Runtime_73615/C IL_000f: callvirt instance void Runtime_73615/C::Baz(int32) IL_0014: ret } ``` Before: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000004] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000002] --C-G------ arg1 └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ``` After: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000005] -AC-G------ ▌ ASG int [000004] D------N--- ├──▌ LCL_VAR int V02 tmp1 [000002] --C-G------ └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ***** BB01 STMT00001 ( ??? ... ??? ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000007] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000006] ----------- arg1 └──▌ LCL_VAR int V02 tmp1 ``` Fix dotnet#73615
This needs to wait for dotnet/roslyn#63221 for a runtime side fix. As of writing this the C# compiler has been fixed, but not the VB.NET compiler. |
I'm going to move this to 9.0 since it's blocked waiting for a Roslyn VB.NET fix. |
@jakobbotsch FYI, the fix for dotnet/roslyn#63221 for VB got merged into Roslyn. |
Constrained calls in IL include an implicit dereference that occurs as part of the call. The JIT was adding this dereference on top of the 'this' argument tree, which makes it happen too early (before other arguments are evaluated). This changes the importer to spill when necessary to preserve ordering. For: ```cil .method private hidebysig static void Foo(class Runtime_73615/C arg) cil managed noinlining { // Code size 21 (0x15) .maxstack 2 IL_0000: ldarga.s arg IL_0002: ldarga.s arg IL_0004: call int32 Runtime_73615::Bar(class Runtime_73615/C&) IL_0009: constrained. Runtime_73615/C IL_000f: callvirt instance void Runtime_73615/C::Baz(int32) IL_0014: ret } ``` Before: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000004] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000002] --C-G------ arg1 └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ``` After: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000005] -AC-G------ ▌ ASG int [000004] D------N--- ├──▌ LCL_VAR int V02 tmp1 [000002] --C-G------ └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ***** BB01 STMT00001 ( ??? ... ??? ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000007] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000006] ----------- arg1 └──▌ LCL_VAR int V02 tmp1 ``` Fix #73615
) Constrained calls in IL include an implicit dereference that occurs as part of the call. The JIT was adding this dereference on top of the 'this' argument tree, which makes it happen too early (before other arguments are evaluated). This changes the importer to spill when necessary to preserve ordering. For: ```cil .method private hidebysig static void Foo(class Runtime_73615/C arg) cil managed noinlining { // Code size 21 (0x15) .maxstack 2 IL_0000: ldarga.s arg IL_0002: ldarga.s arg IL_0004: call int32 Runtime_73615::Bar(class Runtime_73615/C&) IL_0009: constrained. Runtime_73615/C IL_000f: callvirt instance void Runtime_73615/C::Baz(int32) IL_0014: ret } ``` Before: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000004] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000002] --C-G------ arg1 └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ``` After: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000005] -AC-G------ ▌ ASG int [000004] D------N--- ├──▌ LCL_VAR int V02 tmp1 [000002] --C-G------ └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ***** BB01 STMT00001 ( ??? ... ??? ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000007] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000006] ----------- arg1 └──▌ LCL_VAR int V02 tmp1 ``` Fix dotnet#73615
) Constrained calls in IL include an implicit dereference that occurs as part of the call. The JIT was adding this dereference on top of the 'this' argument tree, which makes it happen too early (before other arguments are evaluated). This changes the importer to spill when necessary to preserve ordering. For: ```cil .method private hidebysig static void Foo(class Runtime_73615/C arg) cil managed noinlining { // Code size 21 (0x15) .maxstack 2 IL_0000: ldarga.s arg IL_0002: ldarga.s arg IL_0004: call int32 Runtime_73615::Bar(class Runtime_73615/C&) IL_0009: constrained. Runtime_73615/C IL_000f: callvirt instance void Runtime_73615/C::Baz(int32) IL_0014: ret } ``` Before: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000004] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000002] --C-G------ arg1 └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ``` After: ``` ***** BB01 STMT00000 ( 0x000[E-] ... 0x014 ) [000005] -AC-G------ ▌ ASG int [000004] D------N--- ├──▌ LCL_VAR int V02 tmp1 [000002] --C-G------ └──▌ CALL int Runtime_73615:Bar(byref):int [000001] ----------- arg0 └──▌ LCL_ADDR byref V00 arg0 [+0] ***** BB01 STMT00001 ( ??? ... ??? ) [000003] --C-G------ ▌ CALL nullcheck void Runtime_73615+C:Baz(int):this [000007] n---G------ this ├──▌ IND ref [000000] ----------- │ └──▌ LCL_ADDR long V00 arg0 [+0] [000006] ----------- arg1 └──▌ LCL_VAR int V02 tmp1 ``` Fix dotnet#73615
Small example:
Expected: B
Actual: A
Seen in #73606 (comment)
category:correctness
theme:importer
skill-level:intermediate
cost:small
impact:small
The text was updated successfully, but these errors were encountered: