assembly 为什么不允许从一个记忆移动到另一个记忆?

3gtaxfhh  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(162)

我想知道这在组装中是否允许,

movl (%edx) (%eax)

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

leal %esi (%edi)

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

gkl3eglg

gkl3eglg1#

从内存到内存复制的正常/有效方法是加载到临时寄存器。如果在复制后寄存器中不再需要加载地址,您甚至可以使用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必须是寄存器。
(And有一些专门的形式,如op r/m32, imm32,或者具体地对于MOV是movabs r64, [64bit-absolute-address]。)
参见《x86指令集参考手册》(HTML scrape;我在这里使用Intel/NASM语法,因为Intel和AMD的参考手册都使用这种语法。
很少有指令可以对两个不同的地址进行加载和存储,例如movs(字符串移动)和push/pop (mem)(什么x86指令需要两个(或更多)内存操作数?)。在所有这些情况下,至少有一个内存地址是隐式的(由操作码暗示),而不是可以是[eax][edi + esi*4 + 123]或其他的任意选择。
许多ALU指令都有一个内存目的地。这是一个单一内存位置上的读-修改-写,使用相同的寻址模式进行加载和存储。这表明限制并不是8086不能加载和存储,而是解码复杂度(和机器码紧凑性/格式)的限制。

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

一条x86指令最多有一个ModRM字节,一个ModRM只能编码一个寄存器/内存操作数(2位用于模式,3位用于基址寄存器),以及另一个仅限寄存器的操作数(3位)。通过转义码,ModRM可以发送SIB字节来编码内存操作数的基址+按比例变址,但仍然只有空间来编码一个内存操作数。

**如上所述,同一条指令(asm源助记符)的内存源和内存目标形式使用两个不同的操作码。**就硬件而言,它们是不同的指令。

这种设计选择的部分原因可能是实现复杂性:如果一条指令可能需要AGU的两个结果由于没有其它指令可以具有多个r/m操作数,因此,如果r/m操作数是r/m操作数,则r/m操作数可以是r/m操作数,它将花费额外的晶体管(硅面积)来支持编码两个任意寻址模式的方式。2同样对于必须计算出一条指令有多长的逻辑来说,这样它就知道从哪里开始解码下一条。
它还可能为指令提供五个输入相关性(存储地址采用双寄存器寻址模式,加载地址也采用双寄存器寻址模式,如果是adcsbb,则采用FLAGS)。但是,在设计8086 / 80386时,超标量/无序/相关性跟踪可能还未出现在雷达上。386添加了许多新指令,因此,mov的mem-to-mem编码本来可以完成,但没有完成。如果386已经开始将结果直接从ALU输出转发到ALU输入等(与总是将结果提交到寄存器文件相比,减少延迟),那么这个原因可能是它没有实现的原因之一。
如果它存在的话,英特尔P6可能会将其解码为两个独立的微操作,一个加载和一个存储。现在引入它肯定没有意义,或者在1995年之后的任何时候引入它,那时P6已经设计好了,更简单的指令比复杂的指令更有速度优势。(关于如何让代码运行得更快,请参阅http://agner.org/optimize/。)
无论如何,我认为这不是很有用,至少与代码密度的成本相比是这样。**如果你想要这样做,你可能没有充分利用寄存器。**如果可能的话,弄清楚如何在复制的同时动态地处理你的数据。当然,有时你只需要执行一次加载,然后再执行一次存储。例如,在排序例程中,在基于一个成员进行比较后,交换结构体的其余部分。在更大的块中进行移动(例如,使用xmm寄存器)是一个好主意。

leal %esi, (%edi)

这是AT&T的语法lea src, dst,因此lea (%edi), %esimov %edi, %esi的低效等价物,但在另一种顺序中存在两个问题:

首先,寄存器没有地址。空的%esi不是有效的有效地址,因此不是lea的有效源
第二,lea的目的地必须是一个寄存器。没有编码,它需要第二个有效地址来将目的地存储到内存中。

**您在两个操作数之间遗漏了,,**因此,在您对操作数进行限制之前,这是一个障碍。

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

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

dzjeubhm2#

它无效。您不能在我熟悉的任何体系结构上直接执行内存到内存的移动,除非使用有限的操作数集。例外是字符串move等,例如,通过Intel兼容处理器上的SIDI寄存器。尽管这些应该避免(见下文)。大多数体系结构确实有一些东西来帮助这些有限的内存到内存的移动。
如果你考虑一下硬件,这就很有意义了。有地址线和数据线。处理器在地址线上发出要访问哪个存储器地址的信号,然后通过数据线读取或写入数据。因为数据必须通过该高速缓存或处理器才能到达其他存储器。实际上,如果你看一下第145页的this reference,您将看到一条强有力的声明,即绝对不能使用MOVS及其朋友:
请注意,REP MOVS指令将一个字写入目标时,它会在同一个时钟周期内从源读取下一个字。如果P2和P3上的这两个地址中的位2-4相同,则可能会发生缓存组冲突。换句话说,如果ESI+WORDSIZE-EDI可被32整除,则每次迭代将额外增加一个时钟。避免缓存区冲突的最简单方法是将源地址和目标地址按8对齐。切勿在优化代码中使用MOVSB或MOVSW,即使在16位模式下也是如此。
在许多处理器上,REP MOVS与REP STOS可以通过一次移动16个字节或整个缓存线来快速执行。只有在满足某些条件时才会发生这种情况。根据处理器的不同,快速字符串指令的条件通常包括:计数必须很高、源源和目标之间的距离必须至少为该高速缓存线大小,并且源和目标的内存类型必须为回写或组合写入(通常可以假设满足后一个条件)。
在这些条件下,速度与向量寄存器移动一样快,甚至在某些处理器上更快。虽然字符串指令非常方便,但必须强调的是,在许多情况下,其他解决方案更快。如果不满足上述快速移动的条件,那么使用其他方法会有很多好处。
从某种意义上说,这也解释了为什么寄存器到寄存器的移动是可以的(尽管还有其他原因)。也许我应该说,这解释了为什么它们不需要非常特殊的硬件在板上...寄存器都在处理器中;不需要通过地址访问总线来读取和写入。

相关问题