我不明白这种寻址模式的实际需要。为什么我们不能通过直接寻址来做到这一点?
你可以; MIPS 只有一种寻址模式,编译器仍然可以为其生成代码。但有时它必须使用额外的 shift + add 指令来计算地址(如果它不只是循环遍历数组)。
寻址模式的重点是保存指令和保存寄存器,尤其是在 x86 这样的 2 操作数指令集中,其中add eax, ecx 用结果 (eax += ecx) 覆盖 eax,这与 MIPS 或其他 3 指令不同addu $t2, $t1, $t0 执行 t2 = t1 + t0 的 ISA。在 x86 上,这需要一个副本 (mov) 和一个 add。 (或者在那种特殊情况下,lea edx, [eax+ecx]: x86 可以使用与内存操作数相同的指令编码进行复制和添加(和移位)。)
考虑一个直方图问题:您以不可预知的顺序生成数组索引,并且必须索引一个数组。在 x86-64 上,add dword [rbx + rdi*4], 1 将使用单个 4 字节指令增加内存中的 32 位计数器,该指令仅解码为 2 微指令,以便前端发出到现代 Intel CPU 上的无序内核. (http://agner.org/optimize/)。 (rbx 是基址寄存器,rdi 是缩放索引)。 scaled 索引非常强大; x86 16 位寻址模式支持 2 个寄存器,但不支持缩放索引。
经典 MIPS 仅具有单独的移位和加法指令,尽管 MIPS32 确实为地址计算添加了缩放加法指令。这将在这里保存一条指令。作为加载-存储机器,加载和存储始终必须是单独的指令(不像在 x86 上,添加解码为微融合加载+添加和存储。请参阅INC instruction vs ADD 1: Does it matter?)。
ARM 可能是 MIPS 的更好比较:它也是一个加载存储 RISC 机器。但它确实有多种寻址模式,包括使用桶形移位器的缩放索引。因此,您无需为每个数组索引单独移位/添加,而是使用具有相同寻址模式的LDR R0, [R1, R2, LSL #2]、add r0, r0, #1/str。
通常在遍历数组时,最好只增加 x86 上的指针。但它也是使用索引的一种选择,特别是对于使用相同索引的多个数组的循环,例如C[i] = A[i] + B[i]。不过,索引寻址模式有时可以是slightly less efficient in hardware,因此当编译器展开循环时,它通常应该使用指针,即使它必须分别递增所有 3 个指针而不是一个索引。
指令集设计的重点不仅在于图灵完备,还在于使高效代码能够以更少的时钟周期和/或更小的代码大小完成更多的工作,或者为程序员提供以其中任何一个目标为目标的选项。
计算机可编程的最低门槛极低,例如各种One instruction set computer 架构。 (没有一个真正实现过,只是在纸上设计,以表明可以编写只有一个减法和分支如果小于零指令的程序,并且在指令中编码了内存操作数。
在易于解码(尤其是并行解码)与紧凑之间需要权衡取舍。 x86 是可怕的,因为它演变为一系列扩展,通常没有太多计划为未来的扩展留出空间。如果您对 ISA 设计决策感兴趣,请查看 Agner Fog 的博客,了解有关为高性能 CPU 设计 ISA 的有趣讨论,该 CPU 结合了 x86 的优点(大量工作与一条指令,例如内存操作数作为ALU 指令)具有 RISC 的最佳特性(易于解码,大量寄存器):Proposal for an ideal extensible instruction set。
在指令字中使用位的方式也需要权衡取舍,尤其是在像大多数 RISC 一样的固定指令宽度 ISA 中。不同的 ISA 做出了不同的选择。
- PowerPC 将大量编码空间用于强大的位域指令,例如
rlwinm(向左旋转并屏蔽位窗口)和大量操作码。 IDK 如果通常无法发音且难以记住的助记符与此相关...
- ARM 使用高 4 位基于条件代码预测执行任何指令。它为the barrel shifter 使用了更多位(第二个源操作数可选地按立即数或来自另一个寄存器的计数移位或旋转)。
- MIPS 的直接操作数比较大,基本简单。
x86 32/64 位寻址模式使用可变长度编码,当有索引时使用额外的字节 SIB(缩放/索引/基础)字节,以及可选的 disp8 或 disp32 立即位移。 (例如,add esi, [rax + rdx + 12340] 需要 2 + 1 + 4 个字节进行编码,而 add esi, [rax] 需要 2 个字节。
x86 16 位寻址模式受到更多限制,并将除了可选的 disp8/disp16 位移之外的所有内容打包到 ModR/M 字节中。
假设我们有一条指令 INC AC。指令中是否指定了 AC 的地址,或者是否有一个特殊的操作码表示“INC AC”并且我们不包括 AC 的地址(累加器)?
是的,某些 ISA 中某些指令的机器代码格式包括隐式操作数。许多机器都有 push / pop 指令隐式使用特定寄存器作为堆栈指针。例如,在 x86-64 的 push rax 中,RAX 是显式寄存器操作数 (encoded in the low 3 bits of the one-byte opcode using the push r64 short form),而 RSP 是隐式操作数。
较旧的 8 位 CPU 通常具有 DECA 之类的指令(用于递减累加器 A)。即该寄存器有一个特定的操作码。这可能与使用 DEC 指令在操作码字节中指定哪个寄存器(就像 x86 在 x86-64 将 short INC/DEC encodings 重新用作 REX 前缀之前所做的一样:注意 64 中的“NE”(不可编码) -dec r32 的位模式列)。但是如果没有正则模式,那么它绝对可以被认为是一个隐式操作数。
有时将事物归入整齐的类别会失败,因此不必太担心使用带有操作码字节的位对于 x86 而言是隐式还是显式。这是一种花费更多操作码空间来节省常用指令的代码大小的方法,同时仍然允许与不同的寄存器一起使用。
一些 ISA 按照惯例只使用某个寄存器作为堆栈指针,没有隐式使用。 MIPS就是这样的。
ARM32(在 ARM 中,不是 Thumb 模式)也在 push/pop 中使用显式操作数。它的 push/pop 助记符只是 store-multiple decrement-before / load-multiple increment-after (LDMIA / STMDB) 的别名,用于实现全降序堆栈。请参阅ARM's docs 了解 LDM/STM,它解释了这一点,以及您可以使用这些指令的一般情况做什么,例如LDMDB 递减一个指针然后加载(与 POP 方向相反)。