Skip to content

Devirtualization isn't acting on devirtualizable calls past the first #39519

@NinoFloris

Description

@NinoFloris

Ran from a VM due to dotnet/BenchmarkDotNet#1499

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.17763.1039 (1809/October2018Update/Redstone5)
Intel Core i7-4980HQ CPU 2.80GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=5.0.100-preview.6.20318.15
  [Host]   : .NET Core 5.0.0 (CoreCLR 5.0.20.30506, CoreFX 5.0.20.30506), X64 RyuJIT
  ShortRun : .NET Core 5.0.0 (CoreCLR 5.0.20.30506, CoreFX 5.0.20.30506), X64 RyuJIT

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  
Method Mean Error StdDev Code Size
Combined 3.6617 ns 0.9868 ns 0.0541 ns 42 B
Separate 3.6139 ns 0.4095 ns 0.0224 ns 42 B
JustMe 0.0000 ns 0.0000 ns 0.0000 ns 6 B

The idea is to do a two step virtual call, once dispatching from this (Combined) and once to a separate sealed class (Separate), JustMe is the control as it devirtualizes correctly.

using System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

namespace test2
{
    public interface IMe 
    {
        bool Hello();   
    }

    public interface IYou 
    {
        IMe You { get; }   
    }

    public sealed class Combined : IMe, IYou
    {
        public IMe You => this;
        public bool Hello() => true;
    }
    
    public sealed class MeImpl : IMe
    {
        public bool Hello() => true;
    }
    
    public sealed class YouImpl : IYou
    {
        public IMe You => new MeImpl();
    }

    [ShortRunJob, DisassemblyDiagnoser(exportGithubMarkdown: true)]
    public class Program
    {
        [Benchmark]
        public bool Combined() => new Combined().You.Hello();
        
        [Benchmark]
        public bool Separate() => new YouImpl().You.Hello();
        
        [Benchmark]
        public bool JustMe() => new MeImpl().Hello();
        
        static void Main(string[] args)
        {
            BenchmarkRunner.Run<Program>();
        }
    }
}

And the resulting asm:

.NET Core 5.0.0 (CoreCLR 5.0.20.30506, CoreFX 5.0.20.30506), X64 RyuJIT

; test2.Program.Combined()
       sub       rsp,28
       mov       rcx,offset MT_test2.Combined
       call      CORINFO_HELP_NEWSFAST
       mov       rcx,rax
       mov       rax,[7FFA0807B738]
       add       rsp,28
       jmp       rax
; Total bytes of code 36
; test2.Combined.Hello()
       mov       eax,1
       ret
; Total bytes of code 6

.NET Core 5.0.0 (CoreCLR 5.0.20.30506, CoreFX 5.0.20.30506), X64 RyuJIT

; test2.Program.Separate()
       sub       rsp,28
       mov       rcx,offset MT_test2.MeImpl
       call      CORINFO_HELP_NEWSFAST
       mov       rcx,rax
       mov       rax,[7FFA0808B7F0]
       add       rsp,28
       jmp       rax
; Total bytes of code 36
; test2.MeImpl.Hello()
       mov       eax,1
       ret
; Total bytes of code 6

.NET Core 5.0.0 (CoreCLR 5.0.20.30506, CoreFX 5.0.20.30506), X64 RyuJIT

; test2.Program.JustMe()
       mov       eax,1
       ret
; Total bytes of code 6

Expectation would be that all benchmarks compile down to mov eax, 1

category:cq
theme:devirtualization
skill-level:expert
cost:large
impact:medium

Metadata

Metadata

Assignees

No one assigned

    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