我不明白这种寻址模式的实际需要。为什么我们不能通过直接寻址来做到这一点?
你可以; MIPS 只有一种寻址模式,编译器仍然可以为其生成代码。但有时必须使用额外的shift +add
计算地址的指令(如果它不仅仅是循环遍历数组)。
寻址模式的要点是保存指令和保存寄存器,特别是在像 x86 这样的 2 操作数指令集中,其中add eax, ecx
覆盖eax
结果(eax += ecx
),与 MIPS 或其他 3 指令 ISA 不同,其中addu $t2, $t1, $t0
does t2 = t1 + t0
。在 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/ http://agner.org/optimize/). (rbx
是基址寄存器,rdi
是一个缩放指数)。有一个scaled索引非常强大; x86 16 位寻址模式支持 2 个寄存器,但不支持缩放索引。
经典 MIPS 仅具有单独的移位和加法指令,尽管 MIPS32 确实添加了用于地址计算的缩放加法指令。这样就可以在这里节省一条指令。作为加载-存储机器,加载和存储始终必须是单独的指令(与 x86 不同,在 x86 上,add 解码为微融合加载+添加和存储。请参阅INC 指令与 ADD 1:重要吗? https://stackoverflow.com/questions/36510095/inc-instruction-vs-add-1-does-it-matter).
与 MIPS 相比,ARM 可能是一个更好的比较:它也是一个加载存储 RISC 机器。但它确实有多种寻址模式可供选择,包括使用桶形移位器的缩放索引。因此,您不需要为每个数组索引单独进行移位/添加,而是使用LDR R0, [R1, R2, LSL #2] http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABCAEDD.html, add r0, r0, #1
/ str
具有相同的寻址方式。
通常,在循环数组时,最好只增加 x86 上的指针。但使用索引也是一种选择,特别是对于使用相同索引的多个数组的循环,例如C[i] = A[i] + B[i]
。索引寻址模式有时可以是硬件效率稍低 https://stackoverflow.com/questions/26046634/micro-fusion-and-addressing-modes不过,因此当编译器展开循环时,它通常应该使用指针,即使它必须分别递增所有 3 个指针而不是一个索引。
指令集设计的重点不仅仅是图灵完备,而是能够实现高效的代码可以用更少的时钟周期和/或更小的代码大小完成更多的工作,或者让程序员可以选择实现这些目标中的任何一个。
计算机可编程的最低门槛非常低,参见例如各种一台指令集计算机 https://en.wikipedia.org/wiki/One_instruction_set_computer架构。 (没有真正实现,只是在纸上设计,以表明可以只使用小于零的减法和分支指令来编写程序,并将内存操作数编码在指令中。
在易于解码(尤其是并行解码)与紧凑之间需要权衡。 x86 很可怕,因为它是作为一系列扩展而演变的,通常没有太多规划为未来的扩展留出空间。如果您对 ISA 设计决策感兴趣,请查看 Agner Fog 的博客,了解有关为高性能 CPU 设计 ISA 的有趣讨论,该 ISA 结合了 x86 的优点(一条指令可完成大量工作,例如,内存操作数作为指令的一部分) ALU指令)具有RISC的最佳特性(易于解码,大量寄存器):理想的可扩展指令集的提案 http://www.agner.org/optimize/blog/read.php?i=421.
在指令字中如何使用位也需要权衡,特别是在像大多数 RISC 一样的固定指令宽度 ISA 中。不同的ISA做出了不同的选择。
- PowerPC 使用大量编码空间来执行强大的位域指令,例如rlwinm https://stackoverflow.com/questions/30896622/understanding-powerpc-rlwinm-instruction(向左旋转并屏蔽位窗口)和大量操作码。我不知道那些通常难以发音且难以记住的助记符是否与此有关......
- ARM 使用高 4 位根据条件代码来预测执行任何指令。它使用更多的位桶式移位器 http://www.davespace.co.uk/arm/introduction-to-arm/barrel-shifter.html(第二个源操作数可以选择通过立即数或来自另一个寄存器的计数进行移位或循环)。
- MIPS 的立即数比较大,而且基本上比较简单。
x86 32/64 位寻址模式使用可变长度编码,当存在索引时会带有额外的字节 SIB(缩放/索引/基址)字节,以及可选的 disp8 或 disp32 立即位移。 (例如。add esi, [rax + rdx + 12340]
需要 2 + 1 + 4 字节进行编码,而编码则需要 2 字节add esi, [rax]
.
x86 16 位寻址模式受到更多限制,并将除可选 disp8/disp16 位移之外的所有内容打包到 ModR/M 字节中。
假设我们有一条指令 INC AC。指令中是否指定了 AC 的地址,或者是否有一个特殊的操作码表示“INC AC”并且我们不包括 AC(累加器)的地址?
是的,某些 ISA 中某些指令的机器代码格式包含隐式操作数。很多机器都有push
/ pop
隐式使用特定寄存器作为堆栈指针的指令。例如,在 x86-64 中push rax
, RAX 是显式寄存器操作数 (使用以下方式编码在一字节操作码的低 3 位中push r64简写 https://github.com/HJLebbink/asm-dude/wiki/PUSH),而 RSP 是隐式操作数。
较旧的 8 位 CPU 通常具有 DECA 等指令(用于递减累加器 A)。即该寄存器有一个特定的操作码。这可能与 DEC 指令相同,操作码字节中的某些位指定哪个寄存器(就像 x86 在 x86-64 重新调整寄存器用途之前所做的那样)短 INC/DEC 编码 https://github.com/HJLebbink/asm-dude/wiki/DEC作为 REX 前缀:请注意 64 位模式列中的“N.E”(不可编码)dec r32
)。但如果没有规则模式,那么它绝对可以被视为隐式操作数。
有时,将事物归入整齐的类别会失败,因此不必太担心对于 x86 是否使用带有操作码字节计数的位是隐式的还是显式的。这是一种花费更多操作码空间来节省常用指令的代码大小的方法,同时仍然允许与不同的寄存器一起使用。
一些 ISA 按照惯例仅使用某个寄存器作为堆栈指针,没有隐式使用。 MIPS是这样的。
ARM32(在 ARM 中,而不是 Thumb 模式)也在压入/弹出中使用显式操作数。它的push/pop助记符只是store-multiple decrement-before / load-multipleincrement-after (LDMIA / STMDB)的别名,用于实现全降序堆栈。看ARM 的文档 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABCAEDD.html对于LDM/STM,它解释了这一点,以及您可以使用这些说明的一般情况做什么,例如LDMDB 递减指针然后加载(与 POP 的方向相反)。