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