【问题标题】:New ISA instructions vs i386 [duplicate]新的 ISA 指令与 i386 [重复]
【发布时间】:2026-02-17 07:55:01
【问题描述】:

我了解今天使用最新编译器(GCC、Intel CC、VC 等)编译的所有 x86 (i386) 二进制文件都将在 Intel 386 机器上运行(允许外部依赖,如操作系统函数等),这说明为什么 YouTube 上有视频显示人们在 20 年前的计算机上安装和运行 Windows 7。

但我不明白 ISA 扩展是如何工作的,例如 MMX 和 SSE。如果程序的代码具有扩展指令,但处理器不支持该指令,那么程序肯定会崩溃(可能是处理器中断进入操作系统)。我能想到的唯一解决方案是程序的二进制文件检查是否支持指令,如果不支持,则执行保证每个平台都支持的最低公分母代码,但肯定可以使二进制文件非常大,那么那些是扩展但现在实际上是标准的指令呢,比如浮点运算?这是否意味着执行 FP 操作的每个 i386 二进制文件也包括软件 FP 仿真?

如果一个程序进行指令支持的检查,那么它在哪里进行呢?如果您有一个紧密的循环,那么您不希望它在循环内执行检查。我不相信编译器足够聪明,还不知道如何在每种情况下进行优化,所以缺乏可见的编译器选项很奇怪。

我在我的 VC 项目设置中找不到用于控制指令发射的编译器或链接器的任何设置或选项。有一些关于优化的内容,但没有什么能提供我期望的控制。

AMD 的“3DNow!”又如何呢?延期? 3DNow 旨在成为 MMX 之上的扩展,因此它有一些独特的指令和自身的方面,但我在我的 VC 编译器或链接器设置中找不到对它的任何引用,所以如果我正在编写代码如何我可以让我的编译器使用 3DNow!指令(而不是 MMX)?

此外,关于 3DNow 的 Wikipedia 文章指出,AMD 从其处理器中删除了一些 3DNow 指令 - 如果存在假设这些指令存在的代码,它是否不再有效?

【问题讨论】:

标签: x86 simd instruction-set


【解决方案1】:

我了解今天使用最新编译器(GCC、Intel CC、VC 等)编译的所有 x86 (i386) 二进制文件都将在 Intel 386 机器上运行(允许外部依赖,如操作系统函数等),这说明为什么 YouTube 上有视频显示人们在 20 年前的计算机上安装和运行 Windows 7。

并非如此。英特尔 386 没有 Windows Vista 或 Windows 7 所需的cmpxchg8b。在特定更新后,Windows 7 需要 SSE2。

但我不明白 ISA 扩展是如何工作的,例如 MMX 和 SSE。如果程序的代码有扩展指令,但处理器不支持该指令,那么程序肯定会崩溃(可能是处理器中断进入操作系统)。我能想到的唯一解决方案是程序的二进制文件检查是否支持指令,如果不支持,则执行保证每个平台都支持的最低公分母代码,但肯定可以使二进制文件非常大,那么那些作为扩展但现在实际上是标准的指令呢?比如浮点运算?

Runtime feature dispatch 是一个东西,在一些程序和库中使用它,但经常编译代码需要某些指令。

即使有运行时特性调度,它也经常用于检测相对较新的扩展,而旧的扩展是强制性的。

/arch:SSE2 在 Visual Studio 2012 及更高版本中是默认设置,因此通常程序不会使用旧版浮点运算。

运行时调度的一个示例是来自 MSVC STL 的 vector_algorithms.cpp,其中在运行时检查 AVX2 和 SSE2 的可用性。在 x86 模式下,它们回退到不发出向量指令的纯 C++ 代码,在 x64 模式下,SSE2 的使用是无条件的,因为 SSE2 是 x64 的基线。

我在我的 VC 项目设置中找不到用于控制指令发射的编译器或链接器的任何设置或选项。有一些关于优化的内容,但没有什么能提供我期望的控制。

/arch 开关。来自 IDE,C/C++ 编译器选项中的“启用增强指令集”。

AMD 的“3DNow!”又如何呢?延期? 3DNow 旨在成为 MMX 之上的扩展,因此它有一些独特的指令和自身的方面,但我在我的 VC 编译器或链接器设置中找不到对它的任何引用,所以如果我正在编写代码如何我可以让我的编译器使用 3DNow!指令(而不是 MMX)?

Visual Studio 在自动矢量化期间无法使用它们(从不使用它们进行自动矢量化)。至于内在形式,令人惊讶的是,您仍然可以在 x86 模式下使用它们(但不能在 x64 模式下使用)。

此外,关于 3DNow 的 Wikipedia 文章指出,AMD 从其处理器中删除了一些 3DNow 指令 - 如果存在假定这些指令存在的代码,它是否不再有效?

为了澄清,在某种意义上删除了指令,因为新处理器没有它们。它们不会被旧处理器的微码更新删除。

删除指令以完全不存在,尝试使用它们可能会引发 #UD 异常,或者它们的编码可能会重新用于其他指令。

【讨论】:

    【解决方案2】:

    据我了解,这些扩展在新出现时并不经常使用。如今,这些扩展是给定的(使用 AMD64 时)或假定的(使用 IA-32 时)。 过去,只有在真正需要这些扩展时,例如在开发数学库时,您才会真正受益于这些功能。在这种情况下,您正在做非常具体的工作,可能会花时间决定如何处理向后兼容性。您基本上有两个选择,或者您通过软件实现提供向后兼容性,或者您不支持旧平台或不支持这些平台上的特定功能。根据Wikipedia,还有第三种选择:

    在其他情况下,操作系统可能会模仿旧处理器的新功能,但通常会降低性能。

    这似乎意味着操作系统会以某种方式捕获不受支持的指令并用软件实现替换它们。来自*的参考链接也已失效且未存档,但我找到了另一个clue

    IA-32 CPU 在运行二进制程序时遇到未知指令会产生异常,即#UD (undefined) exception。此异常归结为类 UNIX 操作系统中的 SIGILL 信号。 模拟一条指令的想法是,钩住这个异常,在异常处理程序中做正确的事情,然后返回主程序。因此,CPU 必须在我们想要的每条指令上生成异常模仿。

    但是,相信 cmets 的答案,这些操作系统实现不再是东西了。

    【讨论】:

    • 当前主流操作系统不会在其#UD(未定义指令)异常处理程序中模拟不受支持的指令。对于没有硬件 x87 支持的 CPU 上的软件浮点来说,这曾经是一件事情,但是对于最初仅出于性能原因使用的指令来说,它太慢了,不值得。操作系统只是提供例如一个信号。 (另外,现在有一种机制,CPUID,让软件检查哪些扩展可用并相应地设置函数指针或其他。例如,这就是 glibc 在支持它的 CPU 上选择 AVX2 或 AVX-512VL strcmp / memcmp / 等的方式。)
    • 除非特别值得一提的是性能,否则您不使用扩展是正确的。很多代码只是为基线 x86-64 编译的,其中包括 SSE2,它是 32 位模式的扩展,但是 64 位的基线。因此 SIMD 无需对 64 位代码进行 CPUID 检查即可使用。 (如今,构建 32 位程序以假设 SSE2 基线无需检查即可使用,这仍然算作 i386 的扩展,即使您也可以将其视为向前发展的基线。)跨度>
    • 如果您想要对您的 CPU 没有的 SIMD 扩展进行软件仿真,您可以在 SDE 或任何类似的仿真层下运行您的程序。 SDE 进行二进制翻译,因此支持的指令很快。 How to test AVX-512 instructions w/o supported hardware?
    • 显然 Windows 在异常处理程序中使用/使用了模拟来修复某些 CPU 上的对齐失败 - 请参阅 SetErrorMode(SEM_NOALIGNMENTFAULTEXCEPT),但不是模拟丢失的指令。