簡體   English   中英

為什么不允許 movl 從內存到內存?

[英]Why isn't movl from memory to memory allowed?

我想知道在裝配中是否允許這樣做,

 movl (%edx) (%eax) 

我猜它會訪問第一個操作數中的內存並放入第二個操作數的內存中,類似於 *a = *b 但我還沒有看到任何處理此類問題的示例,所以我猜這是不允許的。 另外,有人告訴我這是不允許的

 leal %esi (%edi)

這是為什么? 最后,我應該知道是否還有其他類似的功能是不允許的。

movl (mem), (mem)

mov dword [eax], [ecx]    ; or the equivalent in Intel-syntax

無效,因為x86 機器代碼沒有具有兩個地址的mov編碼 (事實上​​,沒有 x86 指令可以有兩種任意尋址模式。)

它有mov r32, r/m32mov r/m32, r32 Reg-reg 移動可以使用mov r32, r/m32操作碼或mov r/m32, r32操作碼進行編碼。 許多其他指令有兩個操作碼,一個 dest 必須是寄存器,另一個 src 必須是寄存器。

(還有一些特殊的形式,比如mov r32, imm32movabs r64, [64bit-absolute-address] 。)

請參閱 x86 指令集參考手冊(x86 標簽維基https://stackoverflow.com/tags/x86/info中的鏈接)。 我在這里使用了 Intel/NASM 語法,因為這就是 insn 參考手冊所做的。

很少有指令可以對兩個不同的地址進行加載和存儲,例如movs (string-move) 和push/pop (mem)哪些 x86 指令需要兩個(或更多)內存操作數? )。 在所有這些情況下,至少一個內存地址是隱式的(由操作碼暗示),而不是可以是[eax][edi + esi*4 + 123]或其他任意選擇。

許多 ALU 指令可用於存儲器目標。 這是對單個內存位置的讀-修改-寫,使用相同的尋址模式進行加載然后存儲。 這表明限制不是 8086 無法加載和存儲,而是解碼復雜性(和機器代碼緊湊性/格式)限制。


沒有采用兩個任意有效地址的指令(即使用靈活尋址模式指定)。 movs有隱式的 source 和 dest 操作數,而push有一個隱式的 dest (esp)。

一條 x86 指令最多有一個 ModRM 字節,一個 ModRM 只能編碼一個 reg/memory 操作數(模式 2 位,基址寄存器 3 位)和另一個僅寄存器操作數(3 位)。 使用轉義碼,ModRM 可以發送一個 SIB 字節來編碼內存操作數的基數 + 縮放索引,但仍然只有空間來編碼一個內存操作數。

正如我上面提到的,同一條指令(asm 源助記符)的內存源和內存目標形式使用兩種不同的操作碼。 就硬件而言,它們是不同的指令。


這種設計選擇的部分原因可能是實現的復雜性:如果單個指令可能需要來自 AGU(地址生成單元)的兩個結果,那么布線必須在那里才能實現。 這種復雜性的一部分在於解碼器找出操作碼是哪條指令,並解析剩余的位/字節以找出操作數是什么。 由於沒有其他指令可以有多個r/m操作數,因此需要額外的晶體管(硅面積)來支持對兩種任意尋址模式進行編碼的方式。 同樣對於必須弄清楚一條指令有多長的邏輯,它知道從哪里開始解碼下一條指令。

它還可能為指令提供五個輸入相關性(存儲地址的兩個寄存器尋址模式,加載地址和加載日期相同)。 在設計 8086 / 80386 時,超標量 / 亂序 / 依賴性跟蹤可能不在雷達上。 386 添加了很多新指令,因此可以完成mov的 mem-to-mem 編碼,但沒有完成。 如果 386 已經開始將結果直接從 ALU 輸出轉發到 ALU 輸入之類的東西(與始終將結果提交到寄存器文件相比減少延遲),那么這個原因將是它沒有實現的原因之一。

如果存在,英特爾 P6 可能會將其解碼為兩個獨立的 uops,一個加載和一個存儲。 現在或在 1995 年設計 P6 之后的任何時候引入,更簡單的指令比復雜的指令獲得更多的速度優勢時,引入它當然沒有意義。 (有關使代碼快速運行的內容,請參閱http://agner.org/optimize/ 。)

無論如何,我看不出這非常有用,至少與代碼密度的成本相比。 如果你想要這個,你可能沒有充分利用寄存器。 如果可能,弄清楚如何在復制時即時處理數據。 當然,有時您只需要先加載然后存儲,例如在排序例程中,在基於一個成員進行比較之后交換結構的其余部分。 在較大的塊中進行移動(例如使用 xmm 寄存器)是一個好主意。


leal %esi, (%edi)

這里有兩個問題:

首先,寄存器沒有地址。 %esi不是有效的有效地址,因此不是lea的有效來源

其次, lea的目的地必須是一個寄存器。 沒有編碼需要第二個有效地址將目標存儲到內存。


順便說一句,也不是有效的,因為你離開了,兩個操作數之間。

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

答案的其余部分僅討論修復該語法錯誤后的代碼。

它無效。 除了有限的一組操作數外,您不能直接在我熟悉的任何架構上執行內存到內存的移動。 例如,通過英特爾兼容處理器上的SIDI寄存器進行的字符串move等例外,盡管這些應該避免(見下文)。 大多數架構確實有一些東西可以幫助這些有限的內存到內存的移動。

如果您考慮硬件,這很有意義。 有地址線和數據線。 處理器在地址線上用信號通知要訪問的存儲器地址,然后通過數據線讀取或寫入數據。 因為這些數據必須通過緩存或處理器才能到達其他內存。 事實上,如果您查看第 145 頁上的參考資料,您會看到絕不能使用MOVS及其朋友的強烈聲明:

請注意,當 REP MOVS 指令向目標寫入一個字時,它會在同一時鍾周期內從源讀取下一個字。 如果 P2 和 P3 上的這兩個地址中的位 2-4 相同,則可能存在緩存組沖突。 換句話說,如果 ESI+WORDSIZE-EDI 可被 32 整除,您將在每次迭代中獲得一個額外時鍾的懲罰。避免緩存組沖突的最簡單方法是將源和目標都對齊 8。切勿在其中使用 MOVSB 或 MOVSW優化的代碼,即使在 16 位模式下也不行。

在許多處理器上,REP MOVS 和 REP STOS 可以通過一次移動 16 個字節或整個緩存行來快速執行。 只有在滿足某些條件時才會發生這種情況。 根據處理器的不同,快速字符串指令的條件通常是:計數必須很高,源和目標都必須對齊,方向必須向前,源和目標之間的距離必須至少為緩存行大小,並且源和目標的內存類型必須是回寫或寫組合(您通常可以假設滿足后一個條件)。

在這些條件下,速度與向量寄存器移動可以獲得的速度一樣高,甚至在某些處理器上甚至更快。 雖然字符串指令可能非常方便,但必須強調的是,在許多情況下,其他解決方案更快。 如果不滿足上述快速移動的條件,那么使用其他方法可以獲得很多收益。

從某種意義上說,這也解釋了為什么注冊到注冊移動是可以的(盡管還有其他原因)。 也許我應該說,它解釋了為什么它們不需要在板上非常特殊的硬件......寄存器都在處理器中; 無需訪問總線即可通過地址進行讀寫。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM