当函数参数是对临时对象或按值复制的临时对象的常量引用时,为什么 MSVC 优化会破坏 SSE 代码?

Why do MSVC optimizations break SSE code when function arguments are const refs to temporaries or temporaries copied by value?

运行 昨天,我将尝试给出清晰简单的示例,这些示例对我来说失败了 MSVC12 (VS2013, 120) MSVC14 (VS2015, 140)一切都是隐式的 /arch:SSE+ 和 x64。

为了便于说明,我将使用已定义的宏 _MM_TRANSPOSE4_PS 将问题简化为一个简单的矩阵转置示例。这个是根据洗牌实现的,而不是移动 L/H 8 字节块。

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 只是一个包含四个 __m128 成员的 POD 结构,所有内容都整齐地对齐在 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:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (§12.6.2 [class.base.init]) persists until the constructor exits. A temporary bound to a reference parameter in a function call (§5.2.2 [expr.call]) persists until the completion of the full expression containing the call.

这意味着它应该存在直到调用环境超出范围,这在函数 returns 之后很远。那么,是什么给了?在所有其他情况下,变量得到 "optimized away",但以下情况除外:

Access violation reading location 0xFFFFFFFFFFFFFFFF

虽然解决方案很明显,但要防止用户像其他一些库一样通过基于指针的传输直接传递临时对象,我希望在不阻塞视图的情况下让它更优雅一些。

您可以向结构添加(非虚拟)成员函数,而不会真正影响布局。所以在结构被破坏的时候加上析构函数打印"I'm here %p",在你的函数中打印"I'm there"。 (包括这个地址,您可以理解正在使用的其他临时副本)。

然后就可以在优化后的代码中观察生命周期了。看看这是不是你的问题:我怀疑糟糕的生命周期实际上意味着什么,因为它所在的位置在你的堆栈框架中仍然是有效地址。

此外,更改 floatnis 应该存在的位最坏情况下可能会给您一个非数字或特殊值,并且在这种情况下矢量处理不会抛出或出错,但会将标志值作为那个坏元素的结果。 没有指针,为什么要解引用-1?

我认为失火是由更有趣的事情引起的。

运行 它在调试器中,看看是什么指令导致的。