【问题标题】:Why IA32 does not allow memory to memory mov? [duplicate]为什么 IA32 不允许内存到内存 mov? [复制]
【发布时间】:2012-08-10 19:21:43
【问题描述】:

在英特尔架构 IA32 中,movl、movw 等指令不允许操作数同时为内存位置。例如,指令 movl (%eax), (%edx) 是不允许的。为什么?

【问题讨论】:

  • ModR/M 字节无法对其进行编码。但是当然你可以把它变成“他们为什么这样做”,好吧..同时,字符串移动(movsbmovswmovsdmovsq)有两个内存参数,但他们'是隐含的。
  • 现在是 1976 年,你可以在一个芯片上放置 20,000 个晶体管来实现一个 16 位处理器。这需要大量偷工减料,非常非正交的设计是结果的一部分。并且没有空间找到缓冲总线周期之间的值所需的存储空间。
  • 我认为更好的解释是使用两种完整寻址模式 (base + index + disp16) 解码 insn 将需要两个 AGU(地址生成单元),并且会使二进制机器码格式复杂化很多。 (哪个段覆盖适用于哪个操作数?对于一个或两个操作数都是寄存器的常见情况,如何允许编码两个内存地址而不会使代码大小膨胀?)
  • @PeterCordes 最初的 8086 甚至没有一个地址生成单元,所以这不是原因。它确实使用 ALU 进行计算。

标签: assembly x86 cpu-architecture instruction-set


【解决方案1】:

答案涉及对 RAM 的更全面了解。简单地说,RAM 只能处于两种状态,读模式或写模式。如果您希望将 ram 中的一个字节复制到另一个位置,当您从读取切换到写入时,您必须在 RAM 之外有一个临时存储区域。

架构当然有可能拥有这样的 RAM 到 RAM 指令,但这将是一个高级指令,在微代码中将转换为将数据从 RAM 复制到寄存器然后再返回到 RAM。或者,可以将 RAM 控制器扩展为具有这样一个临时寄存器用于这种数据复制,但它不会为 CPU/硬件交互增加的复杂性提供太多好处。

编辑:值得注意的是,最近的进步,如混合内存立方体和高带宽内存是其中 RAM 拓扑变得更像 PCI-e 和直接 RAM 到 RAM 传输的架构。可能,但这是由于技术的支持逻辑,而不是 RAM 本身。在 CPU 架构中,这将是一次巨大的 RAM 块的形式,如 DMA,而不是单条指令的形式,加上 CPU 缓存的行为类似于传统 RAM,因此架构必须将其抽象为按照我原来的解释

EDIT2: 根据@PeterCordes 的评论,我最初的理解并不完全正确; x86 实际上确实有一些内存到内存指令。它们不适用于大多数指令(例如 movl 和 movw)的真正原因是保持指令编码的复杂性较低,但它们本可以实现它们。但是,我最初的回答中的基本想法是,在 RAM 之外有一个以锁存器或寄存器形式存在的临时存储位置是正确的,但认为这就是这些指令不存在的原因的想法是不正确的。即使是 1970 年代的较旧芯片(例如 6502 和 8086)也具有内存到内存指令,您可以轻松地直接在 RAM 位置执行诸如 INC 之类的操作。这是通过将内存获取直接锁存到 ALU 并再次返回内存而不通过指令集使用的寄存器来实现的。

【讨论】:

  • 在 x86 中,这是一个 insn 编码限制,同时保持解码复杂度较低。 一个 mem-to-mem 复制指令 (movs),但它使用两个隐式操作数。还有push/pop [mem]从insn中编码的有效地址读取,写入[rsp]。 (反之亦然,push 与 pop)。最初的 8086 有这些 insn,而且它的晶体管预算非常小(但显然大到足以在读取和写入之间锁存 16 位)。在当前的 CPU 中,rep movs 对于大型对齐缓冲区的块复制非常有效。
  • 此外,所有内存目标指令,如inc byte [mem] 都会对内存进行读-修改-写操作。它指向同一个地址,但这仍然是两个单独的命令。这个答案是不错的猜测,但它与正确答案几乎无关。
  • @PeterCordes 感谢您的意见。自从我写了这个答案并意识到我并不完全正确以来,我学到了很多东西。我在答案中添加了一个编辑记录。
  • IA32 使用与 8086 相同的指令集,只是有一些 32 位扩展。 inc dword [eax] 存在于 IA-32 中。 (或 AT&T 语法,incl (%eax)。听起来你的最后一段声称 6502 和 8086 有这个,但 IA-32 没有。
  • @Dougvj:原来我确实已经在movl from memory to memory 上写了一个答案,解释了机器编码和 ISA 设计(以及现代 CPU 上的性能)的原因没有 mem,mem 指令。这个问题是一个确切的问题。我可以而且也许应该在这里重新发布。
【解决方案2】:

ia32 是 x86,x86 是从 intel 8086 (iAPX 86) 演变而来的。它是基于 8 位指令集的小型廉价芯片,没有带有两个显式内存操作数的“mov”。

维基百科的作者对8086的指令编码给出了这样的解释:

由于受 8 位处理器启发的紧凑编码,大多数指令都是单地址或双地址操作,这意味着结果存储在其中一个操作数中。最多一个操作数可以在内存中,但是这个内存操作数也可以是目标,而另一个操作数,源,可以是寄存器或立即数。单个内存位置通常也可以用作源和目标,除其他因素外,这进一步促成了与当时大多数 8 位机器相当(并且通常优于)的代码密度。

有一些带有内存-内存指令的 CISC(一条指令对两个内存操作数进行操作)。讲座https://www.cis.upenn.edu/~milom/cis501-Fall05/lectures/02_isa.pdf 说VAX 可以编码内存-内存指令:

DEC VAX(PDP-11 的虚拟地址扩展):1977

  • • 可变长度指令:1-321 字节!!!
  • • 14 个 GPR + PC + 堆栈指针 + 条件代码
  • • 数据大小:8、16、32、64、128 位、十进制、字符串
  • 内存-内存指令适用于所有数据大小
  • • 特殊insn:crc、insque、polyf 和数百个演员

这是 VAX 的 OpenBSD memcpy 源代码(指令集手册http://h20565.www2.hpe.com/hpsc/doc/public/display?docId=emr_na-c04623178):

https://es.osdn.jp/projects/openbsd-octeon/scm/git/openbsd-octeon/blobs/master/src/sys/lib/libkern/arch/vax/memcpy.S

         movq    8(ap),r1        /* r1 = src, r2 = length */
         movl    4(ap),r3        /* r3 = dst */
... 
 1:      /* move forward */
         cmpl    r2,r0
         bgtru   3f              /* stupid movc3 limitation */
         movc3   r2,(r1),(r3)    /* move it all */

这里的“movc3”指令有两个内存操作数,地址存储在寄存器中。

x86 有几个“字符串”指令将执行内存操作(*s,尤其是 movs - http://x86.renejeschke.de/html/file_module_x86_id_203.html),但该指令将使用预定义的寄存器 SI 和 DI 作为地址(隐式操作数)和两个内存操作数仍然无法在 x86 中编码。

【讨论】:

  • 知道当一条指令只有一个或零个内存操作数时,VAX 机器代码如何保持紧凑?这就是 x86 的问题,原来的 8086 具有非常简单的指令解码。 (这可能需要两个 AGU,或一个 AGU 的两种用途)。 8086 将不得不做一些比 mod/rm 字节更复杂的事情,可能使用像 386 的 SIB 字节这样的可变长度编码。 VAX 显然设法支持scaled-index addressing modes (with or without increment/decrement)!
  • NVM,找到它:“2.2. 寻址模式 VAX-11 支持十六种寻址模式。每个操作数在内存中用一个操作数说明符表示,它由一个模式字节后跟从零到五个附加信息字节。模式字节分为两个字段:一个四位模式说明符和一个四位寄存器指示符。来自(google 的 html 缓存)users.cs.jmu.edu/abzugcx/Public/Student-Produced-Term-Projects/…
  • 彼得,你觉得你自己的正确答案怎么样?
  • 我现在正在写一个答案。 IIRC,这个问题以前因基于意见或其他原因而关闭,否则我已经发布了一个。在回顾我之前在这篇文章中的 cmets 时,我开始意识到我现在应该自己回答。
  • 更新,在我上次发表评论前将近一年,我已经写了一个关于这个的答案:Why isn't movl from memory to memory allowed?。终于碰巧同时注意到了这个旧评论和我的另一个答案。
【解决方案3】:

RAM 支持输入和输出,但不支持复制。因此,内存到内存的移动实际上是内存到 CPU 到内存的移动。理论上可以实现这样的指令,但可能不是因为它不太实用。

以下是实施此类指令需要考虑的一些事项:

  • 我们使用什么临时存储位置?寄存器?

  • 如果我们使用寄存器,我们会劫持哪个寄存器?

不提供这样的指令将上述问题留给程序员。

【讨论】:

  • 但他们确实提供了它,请参阅movsb 及其家族。当然,要使用的寄存器不一定是架构的。
  • 添加到 harold,@Kendall:- 你认为哪个寄存器调用命令劫持?为什么这种东西不能在这里使用???
【解决方案4】:

据我所知,作为这种架构的一般规则,每条指令只允许一次内存访问。这是因为每条指令处理两次内存访问会使处理器的执行流水线复杂化。

【讨论】:

  • 有这样一条规则,不过是关于 Intel 处理器中的 µops 的。
  • SCAS*、MOVS*、PUSH/POP mem、PUSHA/POPA 和其他一些指令确实会访问多个内存“字”。但是它们的内存操作数并不是全部使用 Mod R/M 字节编码的,它最多只能引用一个内存操作数。
猜你喜欢
  • 2016-02-21
  • 2019-02-06
  • 2016-12-28
  • 1970-01-01
  • 1970-01-01
  • 2020-12-11
  • 1970-01-01
  • 2012-10-27
  • 2012-04-22
相关资源
最近更新 更多