Подтвердить что ты не робот

Компилятор Roslyn оптимизирует функцию умножения вызова функции с нулем

Вчера я нашел это странное поведение в своем коде С#:

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 с режимом Release (/optimize) и Debug mode и с различными целевыми фреймворками (4.0, 4.5, 4.6 и 4.6.1). Во всех 8 случаях результат был таким же (1|2|3|0).

Затем я тестировал его под Windows 7 с обновлением Visual Studio 2013 Update 5 (опять же со всеми комбинациями режима Release/Debug и целевой структуры). К моему удивлению, утверждение здесь не оптимизировано и дает ожидаемый результат 1|2|0.

Поэтому я могу заключить, что это поведение не зависит от /optimize и от флага целевой инфраструктуры, а от используемой версии компилятора.

Из интереса я написал аналогичный код на С++ и скомпилировал его с текущей версией gcc. Здесь вызов функции, умноженный на ноль, не оптимизирован, и функция выполнена правильно.

Я думаю, что такая оптимизация была бы действительна только в том случае, если stack.Pop() была чистой функцией (которой она определенно не является). Но я не решаюсь назвать это ошибкой, я предполагаю, что это просто неизвестная мне функция?

Является ли эта "функция" документирована где-нибудь и есть (простой) способ отключить эту оптимизацию?

4b9b3361

Ответ 1

Да, это определенно ошибка. <expr> * 0, не следует оптимизировать в 0, если <expr> имеет побочные эффекты.

Спасибо за сообщение о проблеме!!

Вы можете отслеживать ход ошибки/исправления в https://github.com/dotnet/roslyn/issues/13486