dotnet / dotnet/runtime

JIT: Devirtualization fails after tuple/array construction

Open
#121,990 8 comments 0 reactions 0 assignees View on GitHub
area-CodeGen-coreclr tenet-performance
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

The JIT seems to lose information about values after certain kinds of assignments, resulting in virtual calls that could have been inlined away.

Here is a minimal example:
```csharp
using System;
using System.Runtime.CompilerServices;

public interface IHash {
int Hash();
}

public struct V1 : IHash {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Hash() { return 5; }
}

public struct V2 : IHash {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Hash() { return 7; }
}

public static class C {
public static int M() {
V1 v1;
V2 v2;
return v1.Hash() + v2.Hash();
}
public static int M2() {
V1 v1;
V2 v2;
IHash h1 = v1;
IHash h2 = v2;
return h1.Hash() + h2.Hash();
}
public static int M3() {
V1 v1;
V2 v2;
IHash h1 = v1;
IHash h2 = v2;
var vs = (h1, h2);
var (x1, x2) = vs;
return x1.Hash() + x2.Hash();
}
public static int M4() {
V1 v1;
V2 v2;
IHash h1 = v1;
IHash h2 = v2;
ReadOnlySpan vs = [v1, v2];
return vs[0].Hash() + vs[1].Hash();
}
}
```

Here is the output x86_64 assembly:
```nasm
C:M():int (FullOpts):
mov eax, 12
ret

C:M2():int (FullOpts):
mov eax, 12
ret

C:M3():int (FullOpts):
push rbp
push r15
push r14
push rbx
push rax
lea rbp, [rsp+0x20]
xor ebx, ebx
xor r15d, r15d
mov rdi, 0x79F33C22A9F0 ; V1
call CORINFO_HELP_NEWSFAST
mov r14, rax
mov byte ptr [r14+0x08], bl
mov rdi, 0x79F33C22AAE0 ; V2
call CORINFO_HELP_NEWSFAST
mov rbx, rax
mov byte ptr [rbx+0x08], r15b
mov rdi, r14
mov r11, 0x79F33AF900F0 ; code for IHash:Hash():int:this
call [r11]IHash:Hash():int:this
mov r15d, eax
mov rdi, rbx
mov r11, 0x79F33AF900F8 ; code for IHash:Hash():int:this
call [r11]IHash:Hash():int:this
add eax, r15d
add rsp, 8
pop rbx
pop r14
pop r15
pop rbp
ret

C:M4():int (FullOpts):
push rbp
push r15
push rbx
sub rsp, 16
lea rbp, [rsp+0x20]
xor ebx, ebx
mov qword ptr [rbp-0x20], rbx
mov qword ptr [rbp-0x18], rbx
xor r15d, r15d
mov rdi, 0x79F33C22A9F0 ; V1
call CORINFO_HELP_NEWSFAST
mov byte ptr [rax+0x08], bl
mov gword ptr [rbp-0x20], rax
mov rdi, 0x79F33C22AAE0 ; V2
call CORINFO_HELP_NEWSFAST
mov byte ptr [rax+0x08], r15b
mov gword ptr [rbp-0x18], rax
mov rdi, gword ptr [rbp-0x20]
mov r11, 0x79F33AF90100 ; code for IHash:Hash():int:this
call [r11]IHash:Hash():int:this
mov ebx, eax
mov rdi, gword ptr [rbp-0x18]
mov r11, 0x79F33AF90108 ; code for IHash:Hash():int:this
call [r11]IHash:Hash():int:this
add eax, ebx
add rsp, 16
pop rbx
pop r15
pop rbp
ret
```
The functions M1 and M2 have been perfectly optimized as the compiler is aware of the types.

It seems to me, that in M3, the tuple was successfully optimized away, as its values are handled via registers. However, the compiler seems to have lost some type information, and thus seems to do boxing and virtual calls in the end after reading back the values.

And in M4, the array seems to not be allocated and stored in the stack, which is good. The iteration over the arrays seems to also have been optimized away. But the values are boxed, and virtual calls are made.

### Configuration

I use .NET 10.0.0-rc.1.25576.99 on https://godbolt.org/z/bYbdebWW6 to view the disassembly.

### Analysis

Not sure what the problem here might be but https://github.com/dotnet/runtime/issues/7541 mentions some in-progress items. Is this covered by those?

### Additional Information

C# does not have support for variadic generics, meaning that we are not able to make function calls with a variable number of arguments of different types and then iterate over those types doing various operations. Thus, the recommended solution is to pack the various values inside an array (possibly in a `params ISomething[]`). However, the JIT seems to not optimize well in those use cases even if such function calls are inlined. Such an optimization could greatly simplify generic code by not having to write it with generics, but rather relying on inlining and type information propagation.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.