TL:DR: 不,AFAIK 没有 RISC ISA 的标志设置部分寄存器操作小于 32 位。但是许多完全具有 FLAGS 的 64 位 RISC ISA(如 AArch64)可以根据 32 位操作的结果设置它们。
见最后一节:这是因为普遍缺乏对软件整数溢出检查的需求,或者鸡/蛋问题。 通常您只需要比较/分支 16 位值,您可以将它们设为零或符号扩展到 32 位或 64 位。
只有寄存器宽度为 8 位或 16 位的 RISC 才能设置该操作数大小的标志。例如 AVR 8 位 RISC 有 32 个寄存器和 16 位指令字。它需要扩展精度的 add/adc 来实现 16-bit int
。
这主要是一个历史问题:x86 的所有操作数大小都是 16 位,因为它是从仅 16 位的 286 演变而来的。在设计 80386 时,重要的是它能够仅运行 16 位全速编写代码,他们提供了将 32 位操作增量添加到 16 位代码的方法。并使用相同的机制在 32 位代码中允许 16 位操作。
x86 8 位低/高寄存器内容 (AX=AH:AL) 再次部分归因于 8086 是如何设计为 8080 的后继产品并使移植变得容易(甚至可以自动化)查看为什么前四个 x86 GPR以如此不直观的顺序命名?. (也因为同时拥有 8 个 1 字节寄存器和4 个 2 字节寄存器非常有用。)
相关:如果只需要结果的低部分,可以使用哪些 2 的补码整数运算而不将输入中的高位归零? 对于许多计算,您不必在每次操作后将高位重新归零以获得相同的结果。因此,缺乏 8 位 / 16 位操作数大小不会成为大多数代码的有效实现的障碍,这些代码在逻辑上将其结果包装为 8 位或 16 位。
64 位 RISC 机器通常至少有一些重要指令的 32 位版本add
,因此您可以免费获得零扩展add
结果,而无需单独截断它,例如使用64 位指针使代码更array[i++]
高效uint32_t i
. 但是,在我听说过的任何 RISC 上,部分寄存器操作数的大小永远不会小于 32 位。
DEC Alpha 很有趣,因为它是一个全新的设计,从头开始 64 位,而不是像 MIPS64 那样对现有 ISA 进行 64 位扩展。这张Alpha 助记符表显示 add/sub/mul/div 在 32 位和 64 位格式中都可用,但移位和比较不可用。(还有字节操作指令,基本上是 64 位整数寄存器中的 SIMD shuffle/mask/insert/extract,以及用于高效字符串内容的 SIMD 打包比较。)
根据这个官方 MIPS64 ISA 文档(第 4.3 节 CPU 寄存器)。
MIPS64 处理器始终产生 64 位结果,即使对于那些在架构上定义为在 32 位上运行的指令也是如此。此类指令通常将其 32 位结果符号扩展为 64 位。这样做时,32 位程序按预期工作,尽管寄存器实际上是 64 位宽而不是 32 位。
(您对完整的 64 位寄存器使用特殊指令,例如DADDU
(doubleword-add unsigned) 而不是ADDU
. 请注意,非 U 版本的add
anddadd
捕获 2 的补码有符号溢出(具有 32 位或 64 位操作数大小),所以你必须使用 U 版本来包装签名数学。(mips.com上的 ISA 参考链接)。无论如何,MIPS 没有 32 位的特殊模式,但操作系统需要关心 32 位程序与 64 位相比,因为 32 位将假定所有指针都位于虚拟地址空间的低 32 位。
在 RISC 加载/存储机器上,您通常只使用零扩展(或符号扩展)字节/半字加载。完成后,您将使用字节/半字存储来获取截断的结果。(对于未签名的 base2 或 2 的补码,通常是您想要的。)这就是编译器(或人类)将如何实现使用short
或uint8_t
.
半相关:C 的整数提升规则会自动提升比用作二元运算符(如 )的操作数时更窄的所有内容int
,int
因此+
它大多很好地映射到这种计算方式。(即,在 C 中,如果 a、b 和 c 都是 ,unsigned result = (a+b) * c
则不必将a+b
结果截断回乘法之前。但是提升为 signed非常糟糕,因此有符号溢出 UB 从提升到有符号的乘法风险.) 无论如何,C 的提升规则看起来像是为不完全支持窄操作数大小的机器设计的,因为这对于许多硬件来说很常见。uint8_t
uint8_t
uint16_t
int
uint16_t a,b; unsigned c = a * b
int
但是你问的是窄操作的溢出检查/标志设置。
并不是所有的 RISC 机器都有一个 FLAGS 寄存器。ARM 有,但例如 MIPS 和 Alpha 没有。ARM 不会在每条指令上设置标志:您必须明确使用指令的标志设置形式。
没有 FLAGS 的 CPU 通常有一些简单的比较和分支指令(通常针对零,如MIPSbltz
),以及比较两个输入并将 0 / 1 结果写入另一个整数寄存器的其他指令(例如 MIPS SLTIU
- 设置为小于立即无符号)。您可以使用 Set 指令 +bne
零来创建更复杂的分支条件。
对有效溢出检查的硬件和软件支持通常是一个问题。在每条 x86 指令之后加上一个jcc
也很糟糕。
但部分原因是大多数语言无法轻松编写需要在每条指令后进行溢出检查的代码,因此 CPU 架构师不会在硬件中提供它,尤其是对于狭窄的操作数大小。
MIPS 对有add
符号溢出的捕获很有趣。
有效实现它的方法可能包括使用“粘性”标志,FPU 异常标志的粘性方式:无效标志在除以零后保持设置(并产生 NaN);其他 FP 指令不清除它。因此,您可以在一系列计算结束时或循环后检查异常标志。如果有软件框架,这使得它足够便宜,可以在实践中实际使用。
使用 FP 代码,通常您不需要查看标志,因为 NaN 本身是“粘性的”或“传染性的”。如果任一输入为 NaN,则大多数二元运算符都会产生 NaN。但是无符号和 2 的补码整数表示没有任何备用位模式:它们都表示特定的数字。(1的补码有负零......)
有关使溢出检查成为可能的 ISA 设计的更多信息,请查看Agner Fog 提出的新 ISA 提案的讨论,该提案结合了 x86(代码密度,每条指令的大量工作)和 RISC(易于解码)的最佳特性一种高性能的纸结构。一些有趣的 SIMD 想法,包括使未来对向量宽度的扩展透明,因此您不必重新编译即可使用更宽的向量更快地运行。