1

是否有任何 RISC 架构允许将算术运算单独应用于字节、半字和其他数据单元,其大小小于 CPU 通用寄存器的大小?

在 Intel x86 (IA-32) 和 x86-64(称为 EM64T 或 AMD64)处理器中,不仅整个寄存器可用,而且其较小的部分也可操作。英特尔 ISA 允许对整个寄存器执行所有算术运算,它是一半、四分之一和一个字节(更准确地说,寄存器中有两个字节可用,例如 RAX 中的 AL 和 AH)。操作完成后,我们可以进行溢出检查,如果之前的操作发生了溢出,可以轻松处理。无论我们是对整个字进行操作(IA-32 为 32 位宽,EM64T 为 64 位宽),还是对较小尺寸的数据(半字、四分之一字或byte),如果结果超过所选数据单元的大小,则相应的标志(OF 或 CF)将设置为 1。

问题是是否有任何 RISC 体系结构可以对小数据进行直接算术运算,这些运算是通过处理器硬件实现的(不需要软件仿真来执行它们),并且在这些运算中发生溢出、进位和借位字节、半字等由处理器设备跟踪,不应以软件方式检查。或者这种方法可能与整个 RISC 理念相矛盾,并且现在和过去都没有 RISC 处理器实现过它?

4

3 回答 3

1

有没有...

您是只谈论市场上的商业 CPU 还是谈论大学的学生项目等?

我自己设计了一个用于教育目的的 RISC CPU,它可以执行 8 位、16 位和 32 位操作。所以这表明至少可以做到这一点。

64 位嵌入式 PowerPC 架构也有类似的东西:它们可以在 64 位寄存器的低 32 位中进行 32 位操作。

此架构没有 8 位和 16 位操作。然而,CISC CPU 也不支持其他宽度较小的计算机支持的所有宽度:

x86 既不支持 4 位操作也不支持 12 位操作,尽管有 CPU(Intel 4004 和 DEC PDP-8)使用这些宽度。

操作完成后,我们可以做一次溢出检查,如果之前的操作发生了溢出,可以轻松处理。

64 位 SPARC 架构在这里很有趣:

为了使 32 位软件能够在 64 位 CPU 上执行,有一些特殊功能。

其中之一是所有标志(进位,零,...)都是重复的:一次用于低 32 位,一次用于整个 64 位。

因此,在执行“添加”操作(只能在 64 位上完成)之后,您可以检查 64 位标志或 32 位标志。

于 2017-12-06T06:52:22.517 回答
1

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 版本的addanddadd捕获 2 的补码有符号溢出(具有 32 位或 64 位操作数大小),所以你必须使用 U 版本来包装签名数学。(mips.com上的 ISA 参考链接)。无论如何,MIPS 没有 32 位的特殊模式,但操作系统需要关心 32 位程序与 64 位相比,因为 32 位将假定所有指针都位于虚拟地址空间的低 32 位。


在 RISC 加载/存储机器上,您通常只使用零扩展(或符号扩展)字节/半字加载。完成后,您将使用字节/半字存储来获取截断的结果。(对于未签名的 base2 或 2 的补码,通常是您想要的。)这就是编译器(或人类)将如何实现使用shortuint8_t.

半相关:C 的整数提升规则会自动提升比用作二元运算符(如 )的操作数时更窄的所有内容intint因此+它大多很好地映射到这种计算方式。(即,在 C 中,如果 a、b 和 c 都是 ,unsigned result = (a+b) * c则不必将a+b结果截断回乘法之前。但是提升为 signed非常糟糕,因此有符号溢出 UB 从提升到有符号的乘法风险.) 无论如何,C 的提升规则看起来像是为不完全支持窄操作数大小的机器设计的,因为这对于许多硬件来说很常见。uint8_tuint8_tuint16_tintuint16_t a,b; unsigned c = a * bint


但是你问的是窄操作的溢出检查/标志设置。

并不是所有的 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 想法,包括使未来对向量宽度的扩展透明,因此您不必重新编译即可使用更宽的向量更快地运行。

于 2017-12-06T06:27:17.017 回答
1

大多数 64 位 RISC 体系结构还通过对 32 位或 64 位字进行操作的指令来支持您所期望的有限形式。许多还支持对位域的操作,尽管我不确定是否允许您直接在位域上进行算术运算

但是有一种名为Blackfin的不规则 RISC 架构,其中数据寄存器可以作为一个整体访问,也可以作为多个单独的部分使用。从它的文档中(为了便于阅读,我将其格式化为项目符号):

  • 累加器:一组 40 位寄存器A1A0通常包含被操作的数据。每个累加器可以通过五种方式访问​​:
    • 作为一个 40 位寄存器
    • 作为一个 32 位寄存器(指定为A1.WA0.W
    • 作为两个 16 位寄存器,类似于数据寄存器(指定为A1.HA1.LA0.HA0.L
    • 并作为一个 8 位寄存器(指定为A1.XA0.X),用于超出位 31 的位。
  • 数据寄存器:一组 32 位寄存器(R0R1R2R3R4R5R6R7),通常包含用于操作的数据。缩写为 D-register 或 Dreg。
    • 数据寄存器可以访问为
      • 32 位寄存器
      • 或可选地作为两个独立的 16 位寄存器。
    • 每个寄存器的最低有效 16 位称为“低”半部分,并在寄存器名称后用“.L”表示。最高有效的 16 位称为“高”半位,并在名称后面标有“.H”。示例:R7.L, r2.h, r4.L, R0.h.

Blackfin 寄存器

它甚至在算术状态(ASTAT) 寄存器中有多个独立的进位和溢出标志,因此更容易混合算术运算


另一个有趣的案例是SuperH SH-5它在通用寄存器中执行 SIMD 操作,尽管它有一组单独的 64 个浮点寄存器。所以你可以对真实的字节/字/双字进行算术运算。换句话说,它在硬件上做SWAR 技术

SH-5 通用寄存器中的多媒体数据


Intel i960也有其独特之处。它是唯一具有 32 个寄存器但没有零寄存器的奇怪 RISC 架构,它具有比较字节和短路的指令,即使它仍然不能对字节进行其他算术运算

cmpi    Compare Integer
cmpib   Compare Integer Byte
cmpis   Compare Integer Short
cmpo    Compare Ordinal
cmpob   Compare Ordinal Byte
cmpos   Compare Ordinal Short
concmpi Conditional Compare Integer
concmpo Conditional Compare Ordinal
于 2019-05-04T16:48:10.867 回答