-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Closed
Labels
Area-CompilersCode Gen QualityRoom for improvement in the quality of the compiler's generated codeRoom for improvement in the quality of the compiler's generated codeFeature - Collection Expressionshelp wantedThe issue is "up for grabs" - add a comment if you are interested in working on itThe issue is "up for grabs" - add a comment if you are interested in working on it
Milestone
Description
Version Used: current main on sharplab (19 Jan 2024, it doesn't say the version or commit 😔)
Steps to Reproduce:
For this code:
List<int> M()
{
return [1, 2, 3, 4];
}the compiler currently emits the equivalent of this:
List<int> M()
{
List<int> list = new List<int>();
CollectionsMarshal.SetCount(list, 4);
Span<int> span = CollectionsMarshal.AsSpan(list);
int num = 0;
span[num] = 1;
num++;
span[num] = 2;
num++;
span[num] = 3;
num++;
span[num] = 4;
num++;
return list;
}(sharplab)
I think it would be better if it generated the equivalent of this instead:
List<int> M()
{
List<int> list = new List<int>();
CollectionsMarshal.SetCount(list, 4);
Span<int> span = CollectionsMarshal.AsSpan(list);
ref int num = ref MemoryMarshal.GetReference(span);
num = 1;
num = ref Unsafe.Add(ref num, 1);
num = 2;
num = ref Unsafe.Add(ref num, 1);
num = 3;
num = ref Unsafe.Add(ref num, 1);
num = 4;
num = ref Unsafe.Add(ref num, 1);
return list;
}or, as an alternative:
List<int> M()
{
List<int> list = new List<int>();
CollectionsMarshal.SetCount(list, 4);
Span<int> span = CollectionsMarshal.AsSpan(list);
ref int num = ref MemoryMarshal.GetReference(span);
Unsafe.Add(ref num, 0) = 1;
Unsafe.Add(ref num, 1) = 2;
Unsafe.Add(ref num, 2) = 3;
Unsafe.Add(ref num, 3) = 4;
return list;
}Why?
- It is more efficient, because the JIT currently doesn't elide range checks in the current codegen. You can see this in the sharplab example.by switching to assembler. This could of course be fixed (and probably should be), but I would argue that the compiler should emit the simpler code anyway, to not provide any chance whatsoever for the JIT to fail to do that.
- It results in a lot less IL. This is important in today's age when .NET runs in environments like the browser. You can see this on sharplab - 46 lines of IL vs 29 and 27, respectively (and the resulting x64 asm is 55, 47 and 45 lines).
I understand that the compiler doesn't usually do optimizations and leaves that to the JIT, but:
- In this case, the compiler doesn't need to do any complicated (or even simple) analysis to generate the desired code. It would simply switch from one codegen strategy to another, both of which have equal complexity.
- One could argue that even the current codegen strategy is an optimization - to generate better code than repeatedly calling
list.Add. And the compiler uses theUnsafe.Addapproach for collection literals for spans today already.
It would also be nice to get rid of the last, unused num++; or num = ref Unsafe.Add(ref num, 1);. The JIT elides this of course, but as I mentioned, generating less IL is better, and in this case that last increment is simply redundant.
Metadata
Metadata
Assignees
Labels
Area-CompilersCode Gen QualityRoom for improvement in the quality of the compiler's generated codeRoom for improvement in the quality of the compiler's generated codeFeature - Collection Expressionshelp wantedThe issue is "up for grabs" - add a comment if you are interested in working on itThe issue is "up for grabs" - add a comment if you are interested in working on it