Roslyn 编译器用零优化函数调用乘法
Roslyn compiler optimizing away function call multiplication with zero
昨天我在我的 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 下测试了它(再次使用 Release/Debug 模式和目标框架的所有组合)。令我惊讶的是,这里的语句 not 被优化掉并产生了预期的结果 1|2|0
.
所以我可以得出结论,此行为既不依赖于 /optimize
也不依赖于目标框架标志,而是依赖于使用的编译器版本。
出于兴趣,我用 C++ 编写了类似的代码,并使用当前的 gcc 版本对其进行了编译。这里乘以零的函数调用没有被优化掉,函数被正确执行。
我认为这种优化只有在 stack.Pop()
是一个纯函数(它肯定不是)的情况下才有效。但我不太愿意将此称为错误,我认为这只是我不知道的一个功能?
这 "feature" 是否有任何记录,是否有(简单的)方法来禁用此优化?
是的,这绝对是一个错误。
* 0,如果有副作用,不应优化为0。
感谢您报告问题!!
您可以在 bug/fix 跟踪进度
https://github.com/dotnet/roslyn/issues/13486
昨天我在我的 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 下测试了它(再次使用 Release/Debug 模式和目标框架的所有组合)。令我惊讶的是,这里的语句 not 被优化掉并产生了预期的结果 1|2|0
.
所以我可以得出结论,此行为既不依赖于 /optimize
也不依赖于目标框架标志,而是依赖于使用的编译器版本。
出于兴趣,我用 C++ 编写了类似的代码,并使用当前的 gcc 版本对其进行了编译。这里乘以零的函数调用没有被优化掉,函数被正确执行。
我认为这种优化只有在 stack.Pop()
是一个纯函数(它肯定不是)的情况下才有效。但我不太愿意将此称为错误,我认为这只是我不知道的一个功能?
这 "feature" 是否有任何记录,是否有(简单的)方法来禁用此优化?
是的,这绝对是一个错误。
感谢您报告问题!!
您可以在 bug/fix 跟踪进度 https://github.com/dotnet/roslyn/issues/13486