roslyn: Multiple `+ sizeof()` not being compiled down to a single add instruction in conjunction with an argument
Version Used: should be the latest
Steps to Reproduce:
- Write some code
public int Calc(int n) {
return n + sizeof(byte) + sizeof(ushort) + sizeof(int);
}
- ildasm it
// Methods
.method public hidebysig
instance int32 Calc (
int32 n
) cil managed
{
// Method begins at RVA 0x2050
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.1
IL_0001: ldc.i4.1
IL_0002: add
IL_0003: ldc.i4.2
IL_0004: add
IL_0005: ldc.i4.4
IL_0006: add
IL_0007: ret
} // end of method C::Calc
- see the multiple adds and be sad
Expected Behavior:
// Methods
.method public hidebysig
instance int32 Calc (
int32 n
) cil managed
{
// Method begins at RVA 0x2050
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.1
IL_0001: ldc.i4.7
IL_0002: add
IL_0007: ret
} // end of method C::Calc
Actual Behavior:
// Methods
.method public hidebysig
instance int32 Calc (
int32 n
) cil managed
{
// Method begins at RVA 0x2050
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.1
IL_0001: ldc.i4.1
IL_0002: add
IL_0003: ldc.i4.2
IL_0004: add
IL_0005: ldc.i4.4
IL_0006: add
IL_0007: ret
} // end of method C::Calc
thankfully the JIT ASM is properly adding 0x7
C.Calc(Int32)
L0000: mov eax, edx
L0002: add eax, 0x7
L0005: ret
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 34 (34 by maintainers)
Ah, I see; and indeed, if we change it to
return sizeof(byte) + sizeof(ushort) + sizeof(int) + n;
, the compiler folds the expression to7 + n
.Maybe it would be worth updating the analyzers/code-fixes to take this into account…
Yes it is. The JIT only happens at runtime. There may be other intermediate processes that can run before (like ngen, crossgen, aot, etc) and which can pass additional information to the JIT; but when it comes to the JIT itself, it has to take exactly what it was given and work with it at runtime.
For some optimizations, yes. I completely agree that having an intermediate and dedicated
ilopt.exe
would be great. It should take the IL and do analysis on it. It should do all the simple optimizations (like constant folding) and it should try to do more expensive analysis and leave hints/breadcrumbs to the JIT where possible. However, we have no such team to do that today and not all optimizations are possible to do at this level.@CyrusNajmabadi, because it can impact multiple things downstream in the runtime/JIT. The JIT, due to time constraints/etc has to take a very narrow view of things when determining inlining and certain other optimizations (unlike an AOT compiler, it doesn’t have the ability to do all the analysis itself).
If the C# compiler has a case where it can produce better/smaller IL and remove the need for the JIT to do the additional analysis/etc, then it can have a positive impact downstream (the method has a higher chance of being inlined, the JIT doesn’t need to produce as many nodes, it doesn’t have to fold the values together, itself etc). This also means that, since the JIT doesn’t have to do that work itself, it can instead spend that time on optimizations that the C# compiler can’t or won’t make.
This seems like a farily trivial case and something that the compiler already should be handling (and it indeed does in some cases).
@SirJosh3917 Constant calculation can be forced via: