Skip to content

Final method won't devirt when call-site is "complex" #11711

@gfoidl

Description

@gfoidl

In the below example the call res[0] = this.Do(src[0]); won't be inlined, whilst res[0] = this.Do('B'); will be inlined.

using System.Linq;
using System.Runtime.CompilerServices;

namespace ConsoleApp1
{
    class Program
    {
        static int Main(string[] args)
        {
            var foo = new Foo();
            var res = Do(foo);

            return res[0];
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static char[] Do(Foo foo)
        {
            //return foo.DoWillDevirt();
            return foo.DoWontDevirt();
        }
    }

    public abstract class Base
    {
        private char[] _src = Enumerable.Repeat('B', 10).ToArray();
        private char[] _res = new char[10];

        public char[] DoWontDevirt()
        {
            char[] src = _src;
            char[] res = _res;

            res[0] = this.Do(src[0]);

            return res;
        }

        public char[] DoWillDevirt()
        {
            char[] src = _src;
            char[] res = _res;

            res[0] = this.Do('B');

            return res;
        }

        public abstract char Do(char c);
    }

    public class Foo : Base
    {
        public sealed override char Do(char c) => (char)(c + 1);
    }
}

Excerpt of JIT dump:

impDevirtualizeCall: Trying to devirtualize virtual call:
    class for 'this' is Base (attrib 21000400)
    base method is Base::Do
    devirt to Base::Do -- inexact or not final
               [000021] --C-G-------              *  CALLV ind int    Base.Do
               [000019] ------------ this in rdi  +--*  LCL_VAR   ref    V00 this         
               [000020] ------------ arg1         \--*  LCL_VAR   int    V02 loc1         
    Class not final or exact, and method not final
NOT Marking call [000021] as guarded devirtualization candidate -- disabled by jit config
INLINER: during 'impMarkInlineCandidate' result 'failed this call site' reason 'target not direct' for 'Base:DoWontDevirt():ref:this' calling 'Base:Do(ushort):ushort:this'
INLINER: during 'impMarkInlineCandidate' result 'failed this call site' reason 'target not direct'

For DoWillDevirt:

impDevirtualizeCall: Trying to devirtualize virtual call:
    class for 'this' is Foo (attrib 21000000)
    base method is Base::Do
    devirt to Foo::Do -- final method
               [000022] --C-G-------              *  CALLV ind int    Base.Do
               [000020] ------------ this in rdi  +--*  LCL_VAR   ref    V00 this         
               [000021] ------------ arg1         \--*  CNS_INT   int    66
    final method; can devirtualize

So the difference is that in the former case the JIT won't recognize the current type class for 'this' is Base vs. class for 'this' is Foo.

In the IL there is nothing special for either case:

.method public hidebysig
    instance char[] DoWontDevirt () cil managed
{
    // Method begins at RVA 0x2070
    // Code size 28 (0x1c)
    .maxstack 4
    .locals init (
        [0] char[],
        [1] char
    )

    IL_0000: ldarg.0
    IL_0001: ldfld char[] ConsoleApp1.Base::_src
    IL_0006: ldarg.0
    IL_0007: ldfld char[] ConsoleApp1.Base::_res
    IL_000c: stloc.0
    IL_000d: ldc.i4.0
    IL_000e: ldelem.u2
    IL_000f: stloc.1
    IL_0010: ldloc.0
    IL_0011: ldc.i4.0
    IL_0012: ldarg.0
    IL_0013: ldloc.1
    IL_0014: callvirt instance char ConsoleApp1.Base::Do(char)
    IL_0019: stelem.i2
    IL_001a: ldloc.0
    IL_001b: ret
}
.method public hidebysig
    instance char[] DoWillDevirt () cil managed
{
    // Method begins at RVA 0x2098
    // Code size 25 (0x19)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldfld char[] ConsoleApp1.Base::_src
    IL_0006: pop
    IL_0007: ldarg.0
    IL_0008: ldfld char[] ConsoleApp1.Base::_res
    IL_000d: dup
    IL_000e: ldc.i4.0
    IL_000f: ldarg.0
    IL_0010: ldc.i4.s 66
    IL_0012: callvirt instance char ConsoleApp1.Base::Do(char)
    IL_0017: stelem.i2
    IL_0018: ret
}

Build for coreclr was done at commit d4cab6e (from yesterday).

If this is already tracked in https://github.com/dotnet/coreclr/issues/9908 please feel free to close this issue here.

/cc: @AndyAyersMS

category:cq
theme:inlining
skill-level:expert
cost:medium

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions