Skip to content

Excessive/strange null reference checks after method inlining #10461

@voinokin

Description

@voinokin

Consider the following repro example (this is actually from the live system, just narrowed down significantly):

        interface ISortKeyOps<TComparableKeyPart>
        {
            TComparableKeyPart EmptyComparableKeyPart { get; }
            byte GetComparableKeyPartsLCPInfo(TComparableKeyPart left, ref TComparableKeyPart right);
        }

        struct SortKeyOps_UInt64 : ISortKeyOps<ulong>
        {
            public ulong EmptyComparableKeyPart { get { return 0; } }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public byte GetComparableKeyPartsLCPInfo(ulong left, ref ulong right) => 0x42;
        }

        struct SortKeysClassifier<TKeyOps, TComparableKeyPart>
            where TKeyOps : ISortKeyOps<TComparableKeyPart>
            where TComparableKeyPart : struct
        {
            TKeyOps KeyOps;
            ulong CurrentKeyPartOfst;
            TComparableKeyPart[] SplittersTree;

            public SortKeysClassifier(TKeyOps keyOps)
            {
                KeyOps = keyOps;
                CurrentKeyPartOfst = 5;
                SplittersTree = new TComparableKeyPart[0];
            }

            unsafe public byte Build()
            {
                var emptyKey = KeyOps.EmptyComparableKeyPart;
                return KeyOps.GetComparableKeyPartsLCPInfo(KeyOps.EmptyComparableKeyPart, ref emptyKey);
            }
        }

        unsafe static void Main(string[] args)
        {
            var keyOps = new SortKeyOps_UInt64();
            var c = new SortKeysClassifier<SortKeyOps_UInt64, ulong>(keyOps);
            var v = c.Build();
            Console.WriteLine(v);
        }

When the nested code within method Build() is inlined with .NET Core 2.1 (preview/RC/RTM) JIT, the following machine code is observed:

--- ...\Program2.cs ---
                var emptyKey = KeyOps.EmptyComparableKeyPart;
000007FE69DF1850  cmp         dword ptr [rcx],ecx  
                return KeyOps.GetComparableKeyPartsLCPInfo(KeyOps.EmptyComparableKeyPart, ref emptyKey);
000007FE69DF1852  cmp         dword ptr [rcx],ecx  
000007FE69DF1854  cmp         dword ptr [rcx],ecx  
000007FE69DF1856  mov         eax,42h  
000007FE69DF185B  ret  
--- No source file -------------------------------------------------------------

Although the code works (disregarding penalty for unneeded operations), there are certain unclarities about this:

  1. It is unclear why checking for null reference more than once (if at all) if this stored in ECX never changes. To add to this - I've seen up to 8-10 such null reference checks stacked when complex expression is involved in full production code.
  2. [The strangest one] ALL null checks disappear, if I remove field SplittersTree and all references to it from structure.

category:cq
theme:value-numbering
skill-level:expert
cost:medium

Metadata

Metadata

Assignees

Labels

JitUntriagedCLR JIT issues needing additional triagearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIenhancementProduct code improvement that does NOT require public API changes/additionsoptimization

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions