Roslyn编译器优化去除函数调用乘法与零

本文关键字:函数调用 编译器 优化 Roslyn | 更新日期: 2023-09-27 18:05:04

昨天我在我的c#代码中发现了这个奇怪的行为:

Stack<long> s = new Stack<long>();
s.Push(1);           // stack contains [1]
s.Push(2);           // stack contains [1|2]
s.Push(3);           // stack contains [1|2|3]
s.Push(s.Pop() * 0); // stack should contain [1|2|0]
Console.WriteLine(string.Join("|", s.Reverse()));

我假设程序会打印1|2|0,但实际上它打印的是1|2|3|0

查看生成的IL代码(通过ILSpy),可以看到s.Pop() * 0被优化为简单的0:

// ...
IL_0022: ldloc.0
IL_0023: ldc.i4.0
IL_0024: conv.i8
IL_0025: callvirt instance void class   [System]System.Collections.Generic.Stack`1<int64>::Push(!0)
// ...

ILSpy反编译:

Stack<long> s = new Stack<long>();
s.Push(1L);
s.Push(2L);
s.Push(3L);
s.Push(0L); // <- the offending line
Console.WriteLine(string.Join<long>("|", s.Reverse<long>()));

首先,我在Windows 7下使用Visual Studio 2015 Update 3进行了测试,包括发布模式(/optimize)和调试模式以及各种目标框架(4.0,4.5,4.6和4.6.1)。在所有8例中,结果是相同的(1|2|3|0)。

然后我在Windows 7下用Visual Studio 2013 Update 5测试了它(再次使用发布/调试模式和目标框架的所有组合)。令我惊讶的是,这里的语句而不是优化了,并产生了预期的结果1|2|0

所以我可以得出结论,这种行为既不依赖于/optimize也不依赖于目标框架标志,而是依赖于所使用的编译器版本。

出于兴趣,我用c++编写了类似的代码,并使用当前的gcc版本进行了编译。在这里,函数调用乘以0不会被优化掉,函数会被正确执行。

我认为这样的优化将是有效的,如果stack.Pop()是一个纯函数(它肯定不是)。但我不太敢说这是一个bug,我想这只是一个我不知道的功能。

这个"特性"在任何地方都有记录吗?有没有一种(简单的)方法来禁用这个优化?

Roslyn编译器优化去除函数调用乘法与零

是的,这绝对是一个bug。& lt;Expr> * 0,如果有副作用

谢谢你报告这个问题!!

您可以在以下位置跟踪bug/修复的进度https://github.com/dotnet/roslyn/issues/13486