dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.47k stars 4.76k forks source link

Concrete type information does not propagate through struct fields #110290

Open neon-sunset opened 2 days ago

neon-sunset commented 2 days ago

Description

It appears that JIT is not able to propagate concrete type information through struct fields typed as the type's abstract parent. The example below matches the shape of stateless FSharpFunc<T1...Tn> instantiations by F#.

Given reduced repro:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<Benchmarks>(args: args);

[ShortRunJob]
[DisassemblyDiagnoser]
public class Benchmarks {
    [Benchmark]
    public int Example() {
        var wrapper = default(Wrapper2<int, int>);
        wrapper.F1 = MyFuncImpl1.Instance;
        wrapper.F2 = MyFuncImpl2.Instance;

        var v1 = wrapper.F1.Invoke(42);
        var v2 = wrapper.F2.Invoke(v1);

        return v2 + 1;
    }
}

abstract class MyFunc<T, U> {
    public abstract U Invoke(T value);
}

sealed class MyFuncImpl1: MyFunc<int, int> {
    public static readonly MyFuncImpl1 Instance = new();
    public override int Invoke(int value) => value + 4;
}

sealed class MyFuncImpl2: MyFunc<int, int> {
    public static readonly MyFuncImpl2 Instance = new();
    public override int Invoke(int value) => value * 2;
}

struct Wrapper2<T, U> {
    public MyFunc<T, U> F1;
    public MyFunc<T, U> F2;
}

The Tier1-PGO compilation of Example() is

; Benchmarks.Dummy()
       push      rbx
       sub       rsp,20
       mov       rax,2907B8012E8
       mov       rcx,[rax]
       mov       rax,2907B8012F0
       mov       rbx,[rax]
       mov       rax,offset MT_MyFuncImpl1
       cmp       [rcx],rax
       jne       short M00_L01
       mov       edx,2E
       mov       rax,offset MT_MyFuncImpl2
       cmp       [rbx],rax
       jne       short M00_L02
       mov       eax,5C
M00_L00:
       inc       eax
       add       rsp,20
       pop       rbx
       ret
M00_L01:
       mov       edx,2A
       mov       rax,[rcx]
       mov       rax,[rax+40]
       call      qword ptr [rax+20]
       mov       edx,eax
M00_L02:
       mov       rcx,rbx
       mov       rax,[rbx]
       mov       rax,[rax+40]
       call      qword ptr [rax+20]
       jmp       short M00_L00
; Total bytes of code 111

It would be great if JIT could propagate the concrete type here through the field and not rely on DPGO to devirtualize the func types back. This also does not devirtualize on NativeAOT for more than single MyFunc implementation. Thanks!

Configuration

.NET SDK:
 Version:           9.0.100
 Commit:            59db016f11
 Workload version:  9.0.100-manifests.3068a692
 MSBuild version:   17.12.7+5b8665660

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.22631
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\9.0.100\

Regression?

Not tested on .NET 8

dotnet-policy-service[bot] commented 2 days ago

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch See info in area-owners.md if you want to be subscribed.

EgorBo commented 2 days ago

Sadly, JIT is not smart enough in early phases (no SSA/VN info) so it can't look through structs (they're not promoted yet) for exact class info, so PGO+GDV sort of fixes it implicitly (GDV check is folded in a later phase). Not sure what would be the best fix here, perhaps, a bit "late" inlining? early SSA/VN/Struct promotion?