与大多数 ISA 一样,x86 也在不断发展。
一些 ISA 通过重新定义现有的操作码来破坏向后兼容(例如 MIPS64r6 就是这样做的),但这种情况很少见。例如MIPS32r6 / MIPS64r6 就是一个例子:https://en.wikipedia.org/wiki/MIPS_architecture#MIPS32/MIPS64_Release_6 重新定义了几种编码,并删除了一些指令。
x86 从未破坏了向后兼容:Ryzen 或 Skylake-X 仍然可以启动和运行在 8086 上工作的机器代码。这就是 x86 CPU 的一部分含义:另请参阅 The start of x86: Intel 8080 vs Intel 8086?。 (我们只是在谈论机器代码,但如果您以传统 BIOS 模式而不是 UEFI 启动 PC,那么即使是 I/O 设备也会被模拟,因此像早期 DOS 这样的非常早期的 8086 PC 操作系统实际上可能会在本地运行。)
Intel 是planning to drop some legacy IBM-PC hardware emulation support from its chipsets,像 PIC、PIT、A20 门。并且放弃对传统 BIOS 启动 (CSM) 的支持,只支持 UEFI,但 CPU 本身仍将支持切换回实模式。
Intel 和 AMD 将这一点发挥到了极致,以至于在当前 CPU 上,在 16 位和 32 位模式下仍然支持像 SALC(如 sbb al,al 但不更新 FLAGS)这样的 未记录 8086 指令,使用增加宝贵的操作码编码空间,可用于对新指令进行更短的编码。
但是使用新 insn 的 SW 只能在新 HW 上运行。新软件将在当前和未来的硬件上运行,而旧硬件则可以选择与之兼容。 (例如,在 32 位代码中,您可能会避免使用 cmov 或 Pentium Pro 中新增的其他指令,以便您的代码可以在 P5 (i586) Pentium / PMMX 上运行。)
x86-64 设置了包含 SSE2 和 PPro 指令(如 cmov)的新基线。所以幸运的是,64 位代码不必担心与没有这些东西的旧 CPU 兼容,x86-64 需要它们。
包含 AVX2、FMA 和 BMI2(例如 Haswell)的新基线会非常好。如果您的编译器可以在整个代码中的任何地方使用 BMI1/BMI2 来实现更有效的变量计数移位指令等,而不仅仅是像 SIMD 指令那样的几个热循环,那么 BMI1/BMI2 尤其有用。但英特尔仍在销售没有 BMI2 的新 CPU(例如 Pentium/Celeron 版本的 Skylake / Coffee Lake。)
如果没有,那会发生什么?
CPU 不支持的指令通常会出现#UD(未定义)。在类 Unix 操作系统上,您的进程将收到 SIGILL(非法指令信号。
(有趣的事实:original 8086 didn't have a #UD exception;每个字节序列都被解码为 something。)
制作一个能够利用新指令但不会在旧 CPU 上触发非法指令错误的二进制文件的唯一方法是执行运行时 CPU 检测和动态调度。一些编译器可以为您做到这一点。
新指令的编码可能(在旧 CPU 上)看起来像是不同指令的冗余前缀。例如lzcnt 在不支持它的 CPU 上将解码为 rep bsr,它以 bsr 运行。并给出与lzcnt不同的结果!
(英特尔的文档明确指出,未来的 CPU 不能保证像当前 CPU 那样解码带有无意义前缀的指令。这为他们留出了以这种方式进行 ISA 扩展的空间。)
有时对旧 CPU 上无意义的 REP 前缀的静默忽略对 ISA 扩展很有用。例如pause 是 rep nop。它在旧 CPU 上无害地解码非常有用,允许将其放置在自旋循环中而无需检查。类似地,硬件锁省略(事务性内存)解码为仍然可以在旧 CPU 上运行的代码,实际上是执行原子操作而不是开始事务。
另请参阅:Stop the instruction set war,作者:Agner Fog。英特尔通过不发布即将推出的 ISA 扩展的详细信息来搞砸 AMD 的一些历史,因此 AMD 最终开发了自己的不兼容的产品,并花费了更多年时间来为自己的 CPU 添加对新扩展的支持。 (例如,在 Bulldozer 之前,SSSE3 在 AMD CPU 上不可用,这意味着即使是需要新计算机的游戏也无法将其作为基准多年,而 Phenom-II CPU 仍然存在。)
但是否引入了新指令以允许以前无法完成的事情?
8086 是图灵完备的(有限内存除外),因此“无法完成”的最重要形式是寻址更多内存:386 中的 32 位地址、64 位地址(错误 48 虚拟/52 物理)在 x86-64 中。但是这些都是通过引入全新的模式来实现的。他们还引入的新指令是另一回事。
但如果你的意思是“无法有效地完成”:
是的,SIMD 是最重要的例子之一。 MMX,然后是 SSE/SSE2,然后是 SSE4.x。然后 AVX 是两倍宽的向量。并行处理 16 或 32 字节数据的整个向量为 strlen 或 memcmp 之类的东西提供了巨大的加速,而不是一次一个字节的循环。对于很多数组的东西也很有帮助。
AVX2 what is the most efficient way to pack left based on a mask? 是新指令集启用的新技巧的一个有趣示例。例如AVX512 内置了这个操作,而 AVX2 + BMI2 允许使用 pdep/pext 的技巧,这在以前是不可能的。
SSSE3 pshufb 是第一个可变控制 shuffle 指令,从查找表中加载 shuffle-control 可以实现以前无法有效实现的事情。例如Fastest way to get IPv4 address from string.
How to implement atoi using SIMD? 还展示了您可以使用 x86 的 pmaddubsw / pmaddwd 整数乘法 + 水平加法指令执行的一些漂亮操作,以乘以小数位值。
在 8086 之后添加的新指令的早期历史在 a bugfixed fork of an appendix of the NASM manual 中有很好的记录。本附录的current version 删除了每条指令的文本描述,以便为 SIMD 指令腾出空间。 (有很多。)
A.5.118 IMUL: Signed Integer Multiply
IMUL r/m8 ; F6 /5 [8086]
IMUL r/m16 ; o16 F7 /5 [8086]
IMUL r/m32 ; o32 F7 /5 [386]
IMUL reg16,r/m16 ; o16 0F AF /r [386]
IMUL reg32,r/m32 ; o32 0F AF /r [386]
IMUL reg16,imm8 ; o16 6B /r ib [186]
IMUL reg16,imm16 ; o16 69 /r iw [186]
IMUL reg32,imm8 ; o32 6B /r ib [386]
IMUL reg32,imm32 ; o32 69 /r id [386]
IMUL reg16,r/m16,imm8 ; o16 6B /r ib [186]
IMUL reg16,r/m16,imm16 ; o16 69 /r iw [186]
IMUL reg32,r/m32,imm8 ; o32 6B /r ib [386]
IMUL reg32,r/m32,imm32 ; o32 69 /r id [386]
当然,任何 reg32 指令都需要 386 才能进行 32 位扩展,但请注意,imul-immediate 是 186 中的新指令 (imul cx, [bx], 123),而 2 操作数 imul 是 386 中的新指令 (imul cx, [bx]),允许乘法而不破坏 DX:AX,使 AX 不那么“特殊”。
其他 386 条指令,如 movsx 和 movzx 也在使寄存器更加正交方面大有帮助,让您可以有效地将符号扩展到任何寄存器。在此之前,您必须将数据输入 AL 并使用 cbw,或输入 AX 以获取 cwd 以签名扩展至 DX:AX。