Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typeof(T).IsValueType not completely unimpactful in some cases #48605

Closed
stephentoub opened this issue Feb 22, 2021 · 6 comments · Fixed by #63720
Closed

typeof(T).IsValueType not completely unimpactful in some cases #48605

stephentoub opened this issue Feb 22, 2021 · 6 comments · Fixed by #63720
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Milestone

Comments

@stephentoub
Copy link
Member

stephentoub commented Feb 22, 2021

The JIT has an optimization to treat typeof(T).IsValueType as a const based on the T being specified, but in some cases the presence of the check is still having an impact on the generated asm.

Non-sensical repro (derived from much more complicated real-world scenario in dotnet/roslyn#51383):

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[DisassemblyDiagnoser]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);
}

[DisassemblyDiagnoser]
public class Benchmarks
{
    private Wrapper<int> _array = new Wrapper<int>(new int[1]);
    private int _offset = 0;

    [Benchmark]
    public void Test1() => _array.Get1(_offset) = default;

    [Benchmark]
    public void Test2() => _array.Get2(_offset) = default;
}

public struct Wrapper<T>
{
    private T[] _items;

    public Wrapper(T[] item) => _items = item;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ref T Get1(int index)
    {
        return ref _items[index];
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ref T Get2(int index)
    {
        if (typeof(T).IsValueType)
        {
            return ref _items[index];
        }

        return ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_items), index);
    }
}

The non-value type branch in Get2 should in theory be removed completely, but using a recent .NET 6 nightly, this results in:

.NET 6.0.0 (6.0.21.11705), X64 RyuJIT

; Benchmarks.Test1()
       sub       rsp,28
       lea       rax,[rcx+10]
       mov       edx,[rcx+8]
       mov       rax,[rax]
       cmp       edx,[rax+8]
       jae       short M00_L00
       movsxd    rdx,edx
       xor       ecx,ecx
       mov       [rax+rdx*4+10],ecx
       add       rsp,28
       ret
M00_L00:
       call      CORINFO_HELP_RNGCHKFAIL
       int       3
; Total bytes of code 39

.NET 6.0.0 (6.0.21.11705), X64 RyuJIT

; Benchmarks.Test2()
       sub       rsp,28
       lea       rax,[rcx+10]
       mov       edx,[rcx+8]
       mov       rax,[rax]
       cmp       edx,[rax+8]
       jae       short M00_L00
       movsxd    rdx,edx
       lea       rax,[rax+rdx*4+10]
       xor       edx,edx
       mov       [rax],edx
       add       rsp,28
       ret
M00_L00:
       call      CORINFO_HELP_RNGCHKFAIL
       int       3
; Total bytes of code 42

Note the difference between:

       xor       ecx,ecx
       mov       [rax+rdx*4+10],ecx

and

       lea       rax,[rax+rdx*4+10]
       xor       edx,edx
       mov       [rax],edx

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

@dotnet-issue-labeler dotnet-issue-labeler bot added area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI untriaged New issue has not been triaged by the area owner labels Feb 22, 2021
@stephentoub stephentoub changed the title typeof(T).IsValueType not completely removed in some cases typeof(T).IsValueType not completely unimpactful in some cases Feb 22, 2021
@EgorBo
Copy link
Member

EgorBo commented Feb 22, 2021

The non-value type branch in Get2 should in theory be removed completely, but using a recent .NET 6 nightly, this results in:

Well, it's still removed, it's just some extra lea - perhaps related to some of the recent struct improvements work?

@stephentoub
Copy link
Member Author

Well, it's still removed, it's just some extra lea

Right, I meant its impact should be completely removed. But it still has an impact.

@AndyAyersMS
Copy link
Member

This is likely another example highlighting the need for forward substitution in the JIT (#6973).

The statement that gets removed also breaks up a tree and so inhibits tree-based optimizations like address mode building. We don't have anything that will try and glue broken trees back together.

@JulieLeeMSFT
Copy link
Member

@AndyAyersMS so no solution until we implement forward substitution?

@AndyAyersMS
Copy link
Member

Not that I know of, no.

@AndyAyersMS
Copy link
Member

I don't expect us to get to this in .Net 6. So marking as future.

@AndyAyersMS AndyAyersMS removed the untriaged New issue has not been triaged by the area owner label Feb 26, 2021
@AndyAyersMS AndyAyersMS added this to the Future milestone Feb 26, 2021
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this issue Jan 13, 2022
Extend ref counting done by local morph so that we can determine
single-def single-use locals.

Add a phase that runs just after local morph that will attempt to
forward single-def single-use local defs to uses when they are in
adjacent statements.

Fix or work around issues uncovered elsewhere:
* `gtFoldExprCompare` might fold "identical" volatile subtrees
* `fgGetStubAddrArg` cannot handle complex trees
* some simd/hw operations can lose struct handles
* some calls cannot handle struct local args

Addresses dotnet#6973 and related issues. Still sorting through exactly
which ones are fixed, so list below may need revising.

Fixes dotnet#48605.
Fixes dotnet#51599.
Fixes dotnet#55472.

Improves some but not all cases in dotnet#12280 and dotnet#62064.

Does not fix dotnet#33002, dotnet#47082, or dotnet#63116; these require handling multiple
uses or bypassing statements.
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jan 13, 2022
AndyAyersMS added a commit that referenced this issue Feb 4, 2022
Extend ref counting done by local morph so that we can determine
single-def single-use locals.

Add a phase that runs just after local morph that will attempt to
forward single-def single-use local defs to uses when they are in
adjacent statements.

Fix or work around issues uncovered elsewhere:
* `gtFoldExprCompare` might fold "identical" volatile subtrees
* `fgGetStubAddrArg` cannot handle complex trees
* some simd/hw operations can lose struct handles
* some calls cannot handle struct local args
* morph expects args not to interfere
* fix arm; don't forward sub no return calls
* update debuginfo test (we may want to revisit this)
* handle subbing past normalize on store assignment
* clean up nullcheck of new helper

Addresses #6973 and related issues. Still sorting through exactly
which ones are fixed, so list below may need revising.

Fixes #48605.
Fixes #51599.
Fixes #55472.

Improves some but not all cases in #12280 and #62064.

Does not fix #33002, #47082, or #63116; these require handling multiple
uses or bypassing statements.
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Feb 4, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Mar 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants