Почему оптимизации MSVC нарушают код SSE, когда аргументы функции являются константными для временных или временных значений, скопированных по значению?
Столкнувшись с этим вчера, я попытаюсь привести ясные и простые примеры, которые мне не удаются с MSVC12 (VS2013, 120) и MSVC14 (VS2015, 140). Все неявно /arch:SSE+ с x64.
Я упрощу проблему до простого примера транспонирования матрицы с использованием определенных макросов _MM_TRANSPOSE4_PS в целях иллюстрации. Этот реализован в терминах тасов, а не перемещения 8-байтовых блоков L/H.
float4x4 Transpose(const float4x4& m) {
matrix4x4 n = LoadMatrix(m);
_MM_TRANSPOSE4_PS(n.row[0], n.row[1], n.row[2], n.row[3]);
return StoreMatrix(n);
}
matrix4x4
это просто структура POD, содержащая четыре __m128
члены, все аккуратно выровнено по 16-байтовой границе, хотя это несколько неявно:
__declspec(align(16)) struct matrix4x4 {
__m128 row[4];
};
Все это не работает на /O1, /O2 и /Ox:
// Doesn't work.
float4x4 resultsPlx = Transpose( GiveMeATemporary() );
// Changing Transpose to take float4x4, or copy a temporary
float4x4 Transpose(float4x4 m) { ... }
// Trying again, doesn't work.
float4x4 resultsPlx = Transpose( GiveMeATemporary() );
Как ни странно, это работает:
// A constant reference to an rvalue, a temporary
const float4x4& temporary = GiveMeATemporary();
float4x4 resultsPlx = Transpose(temporary);
То же самое касается передач на основе указателей, что логично, поскольку базовые механизмы одинаковы. Соответствующая часть спецификации C++11 - §12.2/5:
Второй контекст, когда ссылка связана с временным. Временный объект, к которому привязана ссылка, или временный объект, являющийся полным объектом для подобъекта, к которому привязан временный объект, сохраняется в течение всего срока действия ссылки, за исключением случаев, указанных ниже. Временная привязка к элементу ссылки в ctor-initializer конструктора (§12.6.2 [class.base.init]) сохраняется до выхода из конструктора. Временная привязка к ссылочному параметру в вызове функции (§5.2.2 [expr.call]) сохраняется до завершения полного выражения, содержащего вызов.
Это означает, что он должен выжить до тех пор, пока вызывающая среда не выйдет из области видимости, что далеко после возврата функции. Итак, что дает? Во всех остальных случаях переменные "оптимизируются", за исключением следующего:
Access violation reading location 0xFFFFFFFFFFFFFFFF
В то время как решение очевидно, не позволяйте пользователю передавать временные данные напрямую с помощью указателей, как некоторые другие библиотеки, я надеялся сделать его немного более элегантным, не забивая &s представление.
1 ответ
Вы можете добавлять (не виртуальные) функции-члены в структуру, не влияя на макет. Поэтому добавьте деструктор, чтобы вывести "I'm here %p", когда структура разрушена, и напечатайте "I'm here" в своей функции. (Включите этот адрес, который вы можете использовать для других временных копий).
Затем вы можете наблюдать время жизни в оптимизированном коде. Посмотрите, не является ли это вашей проблемой: я подозреваю, что плохое время жизни на самом деле что-то значит, потому что место, где оно было, все еще является действительным адресом в вашем фрейме стека.
Кроме того, изменение битов, в которых должен проживать floatnis, в худшем случае может дать вам не число или специальное значение, и в этом случае обработка вектора не выбрасывает или выдает ошибку, а помещает значение флага в качестве результата для этого плохого элемент. Нет указателя, так почему же разыменование -1?
Я думаю, что осечка вызвана чем-то более интересным.
Запустите его в отладчике и посмотрите, какая инструкция вызывает это.