计算所需的最少寻址模式数是多少?

What is the minimum number of addressing modes necessary for computation?

在 x86 汇编程序中,假定您有

为什么需要索引和基指针寻址模式? 据我所知,每个都可以用循环代替。

此外,间接模式 似乎也不是很有用,因为您可以简单地使用直接模式来引用内存地址。首先访问一个包含指向内存地址的指针的寄存器的目的是什么?

简而言之,哪些寻址方式是真正需要的?

虽然理论上'addressing mode'可以用来指代操作数类型,但由于不涉及地址,所以有点混乱。英特尔手册使用'addressing mode'来指代内存寻址,我将使用这个定义。

在汇编中,操作数可以是:

  • 立即数
  • 一个寄存器
  • 内存中的一个值(这里的操作数是地址)

在x86架构中,"addressing mode"只针对最后一类操作数:内存操作数(地址),指的是可以用来计算地址的方法。寻址模式可以概括为一个单一的可配置寻址模式:

address = REG_base + REG_index*n + offset

REG_baseREG_indexnoffset 都是可配置的,并且都可以省略(但显然你至少需要一个)。

address = offset 称为立即寻址、直接寻址或绝对寻址。
address = REG_base 称为寄存器间接寻址。
address = REG_base + REG_index 称为基址加索引寻址。
同样,您可以添加偏移量 (offset) 和比例 (n)。

严格来说,您只需要一种模式即可完成所有操作:注册间接寻址 (address = REG)。有了它,如果你需要访问内存,你可以在寄存器中计算你想要的任何地址,并用它来进行访问。 它还可以通过使用内存代替直接寄存器操作数,并通过算术构造值来代替立即操作数。但是,对于实际的指令集,您仍然需要立即操作数来有效地加载地址,如果您不想要仅指针寄存器,则需要寄存器操作数。

寄存器间接之外的其他寻址方式都是为了方便,确实非常方便:

  • 如果您只需要访问内存中的固定变量,立即寻址可以节省一个寄存器。
  • Base + offset 对于访问对象成员非常有用:您可以将基地址保存在寄存器中并访问具有固定偏移量的单个成员。无需中间计算或注册即可持有会员地址
  • 类似地,索引寻址用于访问数组:您只需更改索引寄存器即可访问数组中的任何值。
  • 使用标度,您可以访问多字节变量(例如:int)数组,无需额外的寄存器或计算。
  • 所有内容的组合可用于访问对象中的数组成员,仍然保留基指针以供潜在访问对象中的其他成员。

这些寻址模式不需要从 CPU 进行太多计算:只需要加法和移位。考虑到 x86 可以在每个周期进行乘法运算,这些运算虽然微不足道,但仍然非常方便。

你只需要一个内存寻址模式,这就是许多经典RISC机器在实践中所做的,例如MIPS、SPARC、RISC-V。他们选择 immediate($register) 是因为固定宽度指令中有立即数的空间。 (有些像 PowerPC 有单独的索引寻址指令,否则你必须在寄存器中进行数学计算才能计算出你想要的地址。)


x86 没有寄存器就做不了什么,所以我认为你不能摆脱寄存器“寻址模式”。一些非常不同的体系结构可能不使用寄存器,而只有堆栈或内存、内存指令。 IDK 他们如何实现指针;也许这样的架构可以做到 memory[memory](C 数组表示法)。

计算可能不需要立即数。您可以使用多个寄存器构造任何值。从零开始(xor eax, eax),inc它得到一个1,左移它到你想要的任何位置,inc设置低位,左移它,等等 所以最坏情况下需要 2*popcount(N) 条指令才能将 N 存入寄存器。请注意,立即移位计数将不可用,因此重复移位一个的明显方法(shl eax,是的,有一个单独的移位编码,或者只使用 add eax, eax ) 将仅取决于最高设置位的位置。所以 log2(N) + popcount(N) 对于明显的转变和增加

绝对(所谓的直接)内存寻址不是最有用的寻址模式。我们可以通过使用一系列指令(见上文)构造地址并使用 [register] 来模拟它。如果我们想减少,我们想放弃它。正如 Jester 指出的那样,将绝对寻址作为我们唯一的形式使用起来会非常不方便(或者不可能?)。

索引显然可用于性能,而非必需:您可以使用单独的指令进行移动和添加。

位移也只是为了性能,所以我们可以去掉它们并强制代码手动添加任何位移。有关方法,请参阅紧接的段落。


我相信 x86 仍然可以通过 只是 register[register] 寻址模式进行任意编程。

使用 register[register]immediate,性能应该不会比完整的 x86 差很多,尽管寄存器压力会更大,因为您需要备用寄存器来更频繁地计算地址。当然还需要更多说明。

如果您打算只有一种内存寻址模式,[reg] 更有用 的选择是 [reg+disp],就像 RISC 使用的那样。这有时可以通过不必计算另一个寄存器中的地址来节省指令(和备用寄存器),并且仍然允许 LEA 复制和添加一个常量,尽管它当前不能移动和添加两个寄存器。

对于 x86 的当前机器代码格式,这需要一个额外字节(或 4 个字节)的机器代码。信号 reg 直接与 [reg]-间接 (mem) 与 [reg+disp8][reg+disp32] 在 ModRM 字节中使用 2 位。


如果对内存的隐式访问不算作寻址模式,您当然可以用 lodsdstosd 模拟 [register],但您将无法执行原子读-修改-写操作。不过,这感觉像是作弊。

还有堆栈(push/pop):我不知道堆栈+寄存器机器是否是图灵完备的,但它肯定不是通常意义上的可编程。当然,如果您修改 e/rsp,您可以再次模拟 [register],但操作数大小的选择少于 lodsb/w/d/q / stosb/w/d/q.

如果包括 16 个 ymm 寄存器,

x86 有相当多的 space 用于在寄存器中存储内容。虽然我想不出一种在不使用内存或立即操作数(对于 vextractf128)的情况下在整数寄存器和 ymm 的高 128b 之间移动数据的方法,但实际上你有更多的 16 个 16B 向量寄存器用于存储堆栈以外的本地状态的槽。尽管如此,它的大小仍然有限,这可能意味着 32 位 386 ISA 中的 8 个 GP 寄存器与 64 位 AVX2 ISA 中的所有 integer/mmx/ymm 寄存器与机器是否图灵完备无关 push/pop、寄存器,除 push/pop.

外,不修改堆栈指针