【问题标题】:Why isn't movl from memory to memory allowed?为什么不允许 movl 从内存到内存?
【发布时间】:2016-02-21 00:31:10
【问题描述】:

我想知道在汇编中是否允许这样做,

 movl (%edx) (%eax) 

我猜它会访问第一个操作数中的内存并放入 第二个操作数的内存,例如 *a = *b 但我没有看到任何处理此类的示例,所以我猜这是不允许的。 另外,我被告知这是不允许的

 leal %esi (%edi)

这是为什么呢?最后,是否还有其他类似的功能我应该知道是不允许的。

【问题讨论】:

  • 阅读指令集参考和“基本架构”手册。事实上,两者都不是有效的,第二个甚至没有意义。
  • 嗯,第一个也不存在,因为ebl 不存在,但我认为这只是一个错字。
  • 从内存移动到内存很好 - 但不是以这种方式。你可以从 [rsi] --> [rdi], [esi]-->[ edi] 或 [si]-->[di],但仅适用于这些寄存器且仅适用于 movsb、movsw、movsd、movsq 指令。您还需要适当地设置 es 和 ds。

标签: assembly x86 cpu-architecture instruction-set


【解决方案1】:

从内存复制到内存的正常/有效方法是加载到临时寄存器中。选一个;如果您在复制后仍然不需要寄存器中的加载地址,您甚至可以movl (%ecx), %ecx / movl %ecx, (%eax)

还有其他方法,例如pushl (%ecx) / popl (%edx) 或为movsd 设置 RSI/ESI 和 RDS/EDI,但速度较慢;通常最好只释放一个临时寄存器,即使这意味着稍后重新加载某些内容,或者甚至存储/重新加载一些其他不常用的值。


为什么 x86 不能为一条指令使用两个显式内存操作数:

movl (mem), (mem)         # AT&T syntax
mov dword [eax], [ecx]    ; or the equivalent in Intel-syntax

无效,因为x86 机器代码没有an encoding for mov 和两个地址。 (事实上​​,没有 x86 指令可以有两种任意寻址模式。)

它有mov r32, r/m32mov r/m32, r32。 Reg-reg 移动可以使用mov r32, r/m32 操作码或mov r/m32, r32 操作码进行编码。许多其他指令也有两种操作码,一种是 dest 必须是寄存器,另一种是 src 必须是寄存器。

(还有一些特殊的形式,比如op r/m32, imm32,或者专门用于mov,movabs r64, [64bit-absolute-address]。)

请参阅 x86 指令集参考手册(HTML scrapex86 tag wiki 中的其他链接)。我在这里使用了 Intel/NASM 语法,因为这是 Intel 和 AMD 的参考手册使用的。

很少有指令可以加载和存储到两个不同的地址,例如movs(字符串移动)和push/pop (mem)What x86 instructions take two (or more) memory operands?)。在所有这些情况下,至少有一个内存地址是隐式的(由操作码隐含),而不是任意选择,可能是 [eax][edi + esi*4 + 123] 或其他。

许多 ALU 指令可用于内存目标。这是对单个内存位置的读取-修改-写入,使用相同的寻址模式进行加载然后存储。这表明限制不是 8086 无法加载和存储,而是解码复杂度(以及机器码紧凑性/格式)的限制。


没有采用两个任意有效地址的指令(即使用灵活的寻址模式指定)。 movs 具有隐式源和目标操作数,push 具有隐式目标 (esp)。

一条 x86 指令最多有一个 ModRM 字节,而一条 ModRM 只能编码一个 reg/memory 操作数(2 位用于模式,3 位用于基址寄存器)和另一个仅寄存器操作数(3 位)。使用转义码,ModRM 可以发送 SIB 字节来编码内存操作数的基数 + 缩放索引,但仍然只有编码一个内存操作数的空间。

如我上面所说,同一条指令(asm source mnemonic)的memory-source和memory-destination形式使用了两种不同的操作码。就硬件而言,它们是不同的指令.


选择这种设计的部分原因可能是实现的复杂性:如果一条指令可能需要来自 AGU(地址生成单元)的两个结果,那么必须有布线才能实现这一点。这种复杂性的一部分在于解码器确定操作码是哪条指令,并解析剩余的位/字节以找出操作数是什么。由于没有其他指令可以有多个r/m 操作数,因此需要额外的晶体管(硅区域)来支持编码两种任意寻址模式的方法。也适用于必须弄清楚一条指令有多长的逻辑,因此它知道从哪里开始解码下一条。

它还可能为指令提供五个输入依赖项(存储地址的两个寄存器寻址模式,加载地址相同,如果它是 adcsbb,则为 FLAGS)。但是在设计 8086 / 80386 时,超标量 / 无序 / 依赖跟踪可能不在雷达上。 386 添加了许多新指令,因此可以完成 mov 的 mem-to-mem 编码,但没有。如果 386 已经开始将结果直接从 ALU 输出转发到 ALU 输入和类似的东西(与总是将结果提交到寄存器文件相比减少延迟),那么这个原因将是它没有实现的原因之一。

如果它存在,英特尔 P6 可能会将其解码为两个独立的微指令,一个加载和一个存储。现在引入当然没有意义,或者在 1995 年 P6 设计之后的任何时候引入,并且更简单的指令比复杂的指令获得更多的速度优势。 (请参阅http://agner.org/optimize/ 了解有关使代码快速运行的内容。)

无论如何,我看不出这很有用,至少与代码密度的成本相比没有。 如果你想要这样,你可能没有充分利用寄存器。如果可能的话,弄清楚如何在复制时动态处理你的数据。当然,有时你只需要先加载然后存储,例如在基于一个成员进行比较之后交换结构的其余部分的排序例程中。在较大的块中进行移动(例如使用 xmm 寄存器)是个好主意。


leal %esi, (%edi)

这里有两个问题:

首先,寄存器没有地址。裸露的%esi 不是有效的有效地址,因此不是lea 的有效来源

其次,lea 的目的地必须是一个寄存器。没有编码需要第二个有效地址来将目的地存储到内存中。


顺便说一句,两者都无效,因为您在两个操作数之间遗漏了,

valid-asm.s:2: Error: number of operands mismatch for `lea'

答案的其余部分仅讨论修复该语法错误后的代码。

【讨论】:

    【解决方案2】:

    这是无效的。除了一组有限的操作数外,您可能不会直接在我熟悉的任何架构上执行内存到内存的移动。例如,通过 Intel 兼容处理器上的SIDI 寄存器,字符串move 等例外,尽管应该避免这些(见下文)。大多数架构确实有一些东西可以帮助这些有限的内存移动。

    如果您考虑硬件,这很有意义。有地址线和数据线。处理器在地址线上发出要访问哪个存储器地址的信号,然后通过数据线读取或写入数据。因为这些数据必须通过缓存或处理器才能到达其他内存。事实上,如果您查看第 145 页上的 this reference,您会看到绝对不能使用 MOVS 及其朋友的强烈声明:

    请注意,当 REP MOVS 指令将一个字写入 目的地,它在同一时钟内从源中读取下一个字 循环。如果位 2-4 在 这两个地址在 P2 和 P3 上。换句话说,你会得到一个 如果 ESI+WORDSIZE-EDI 为 可被 32 整除。避免缓存组冲突的最简单方法是 将源和目标都对齐 8。切勿使用 MOVSB 或 MOVSW 优化的代码,即使在 16 位模式下也不行。

    在许多处理器上,REP MOVS 和 REP STOS 可以通过移动来快速执行 一次 16 个字节或整个高速缓存行。这只发生在 满足一定条件。取决于处理器,条件 对于快速字符串指令,通常计数必须是 高,源和目标必须对齐,方向必须 向前,源和目的地之间的距离必须在 至少缓存行大小,以及源和内存类型 目的地必须是回写或写组合(您可以 通常假设满足后一个条件)。

    在这些条件下,速度是可以达到的最高速度 向量寄存器在某些处理器上移动甚至更快。虽然 字符串指令可以很方便,必须强调 在许多情况下,其他解决方案更快。如果以上条件 如果不满足快速移动,那么使用其他方法可以获得很多收益 方法。

    从某种意义上说,这也解释了为什么寄存器到寄存器移动是可以的(尽管还有其他原因)。也许我应该说,它解释了为什么他们不需要非常特殊的硬件在板上......寄存器都在处理器中;无需访问总线即可通过地址进行读写。

    【讨论】:

    • 我认为您的第二段没有任何意义。这只是一个指令集编码/复杂性问题。我们已经知道一条移动内存->内存的指令movs,您在第一段中提到过。
    • 我为你添加了一些解释和参考。
    • 我熟悉缓存库冲突,来自 Agner Fog 的微架构文档。 rep movs 是实现 memcpy 的一种非常的方法,尤其是。在英特尔 IvyBridge 上,后来他们再次改进了微码。无论如何,谈论地址线与数据线与这个问题无关。如果您谈论的是 CPUDRAM 接口,这甚至都不准确,因为地址是通过与数据相同的线路发送的,并带有一些信号位来说明何时到来。请参阅著名的What Every Programmer Should Know About Memory re: DRAM。
    • 在多路关联 L1 D-cache 内部寻址也更复杂。如果缓存行没有准备好,加载和存储会排队进入加载和存储缓冲区,并且会发生各种事情。在现代 CPU 上,地址线和数据线不会参与movs 指令实际执行的同一时钟周期。
    猜你喜欢
    • 2012-08-10
    • 2016-12-28
    • 1970-01-01
    • 2012-10-27
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    • 2019-02-06
    • 1970-01-01
    相关资源
    最近更新 更多