You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The Jit reorders null checks on field addresses with calls that have return buffers #77650
The C# standard is not really relevant here (nor is my understanding of it complete enough to judge whether it is being violated here w.r.t. IL as emitted). The IL level semantics are, however, clear, and not consistent with the code produced, which is what this bug is about.
I will also note that this behavior of "evaluating the LHS" is not what we see for other (similar) forms of assignments at the C# level:
ProblemOne();// Throws "GetValue"ProblemTwo();// Throws "GetValue"ProblemThree();// Throws "GetValue", not "IndexOutOfRangeException"// etc[MethodImpl(MethodImplOptions.NoInlining)]privatestaticvoidProblemOne(){[MethodImpl(MethodImplOptions.NoInlining)]staticintGetValue(ClassWithFieldscls){thrownewException("GetValue");}ClassWithFieldscls=null;cls.Field=GetValue(cls);}[MethodImpl(MethodImplOptions.NoInlining)]privatestaticvoidProblemTwo(){[MethodImpl(MethodImplOptions.NoInlining)]staticintGetValue(int*cls){thrownewException("GetValue");}int*p=null;*p=GetValue(p);}[MethodImpl(MethodImplOptions.NoInlining)]privatestaticvoidProblemThree(){[MethodImpl(MethodImplOptions.NoInlining)]staticintGetValue(){thrownewException("GetValue");}vara=newint[0];a[1]=GetValue();}
The funny part is that the reproduction depends on the size of the struct. Using structs that are smaller than or exactly 64 bits would exhibit Exception throwing behavior, anything larger — NullReferenceException (see the tests below). Idk, maybe that is what "returning buffers" in the issues's title is about.
As far as I understand, the issue is tightly bound to how the compiler historically violates the lang spec and how JIT historically works, so fixing would be an enormous breaking change, but I find that the current behavior is all over the place:
Consistent behavior when working with properties.
Consistent behavior when working with fields in Debug mode.
Consistent behavior when working with fields that are smaller than 64 bits in Debug mode.
Inconsistent behavior when working with fields that are larger than 64 bits in Release mode.
Maybe at least the last one could be tinkered with? At least everyone would violate the lang spec the same way =) I obviously do not have the statistics, but I doubt a lot of code depends on the specific behavior of null referenced large structs assignment.
Activity
vladd commentedon Nov 10, 2022
Isn’t this an expected behavior?
I’d expect the code to run like this:
In our case, item 1 throws.
danmoseley commentedon Nov 10, 2022
What happens on .NET Framework..
SingleAccretion commentedon Nov 10, 2022
No. It will be easier to see why this is not correct if we look at the IL for
Problem:As can be seen, we must execute the call before
stfld.I would expect full framework to also have this bug, the Jit code responsible for it is very old.
vladd commentedon Nov 10, 2022
@SingleAccretion
I’d assume the C# standard expects this behavior too (§11.19.2):
(This is in contrast to properties where the full evaluation of LHS is impossible because there’s no lvalue there.)
SingleAccretion commentedon Nov 10, 2022
The C# standard is not really relevant here (nor is my understanding of it complete enough to judge whether it is being violated here w.r.t. IL as emitted). The IL level semantics are, however, clear, and not consistent with the code produced, which is what this bug is about.
I will also note that this behavior of "evaluating the LHS" is not what we see for other (similar) forms of assignments at the C# level:
danmoseley commentedon Nov 10, 2022
@MadsTorgersen
vladd commentedon Nov 10, 2022
The
ProblemThreeseems to be ok because array access is in the same bucket as indexer (see next bullet in the standard).jakobbotsch commentedon Nov 20, 2022
I stumbled upon a dotnet/roslyn issue that clarifies the Roslyn vs spec situation here:
dotnet/roslyn#63441 (comment)
quixoticaxis commentedon Jan 31, 2024
The funny part is that the reproduction depends on the size of the
struct. Using structs that are smaller than or exactly 64 bits would exhibitExceptionthrowing behavior, anything larger —NullReferenceException(see the tests below). Idk, maybe that is what "returning buffers" in the issues's title is about.As far as I understand, the issue is tightly bound to how the compiler historically violates the lang spec and how JIT historically works, so fixing would be an enormous breaking change, but I find that the current behavior is all over the place:
Debugmode.Debugmode.Releasemode.Maybe at least the last one could be tinkered with? At least everyone would violate the lang spec the same way =) I obviously do not have the statistics, but I doubt a lot of code depends on the specific behavior of null referenced large structs assignment.
Tests:
UPD. Passing class as an argument is not essential.