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

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

本文关键字:优化 为什么 MSVC 代码 SSE 参数 函数 常量 引用 复制      更新时间:2023-10-16

昨天遇到这个问题,我将尝试给出清晰简单的例子,这些例子在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:

第二个上下文是当引用绑定到临时时。这 引用绑定到的临时或临时 将 Complete 对象绑定到临时绑定的子对象 在引用的生存期内持续存在,除非下面指定。 临时绑定到构造函数中的引用成员 ctor-initializer (§12.6.2 [class.base.init]) 一直存在到 构造函数退出。临时绑定到 函数调用 (§5.2.2 [expr.call]) 一直持续到完成 包含调用的完整表达式。

这意味着它应该一直存在到调用环境超出范围,这是在函数返回很久之后。那么,什么给了呢?在所有其他情况下,变量会被"优化",但以下情况除外:

Access violation reading location 0xFFFFFFFFFFFFFFFF

虽然解决方案很明显,但可以防止用户像其他一些库一样直接使用基于指针的传输传递临时文件,但我曾希望实际上让它更优雅一点,而不会阻塞视图。

您可以向结构添加(非虚拟)成员函数,而不会真正影响布局。因此,添加析构函数以在结构被破坏时打印"我在这里%p",并在函数中打印"我在那里"。(包括此地址,您可以理解正在使用的其他临时副本)。

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

此外,更改浮点数应该存在的位可能会在最坏的情况下给你一个非数字或特殊值,在这种情况下,向量处理不会抛出或出错,而是放置一个标志值作为该坏元素的结果。 没有指针,那么为什么它取消引用 −1 ?

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

在调试器中运行它,看看是什么指令导致这种情况。