我可以使用MOV指令将存储在内存中的数据项移动到我选择的通用寄存器中。
MOV r8, [m8]
MOV r16, [m16]
MOV r32, [m32]
MOV r64, [m64]
现在,不要向我开枪,但以下是如何实现的:MOV r24,[m24]
?(我理解后者不合法)。
在我的示例中,我想移动字符“Pip”,即0x706950h,以注册
rax
。
section .data ; Section containing initialized data
14 DogsName: db "PippaChips"
15 DogsNameLen: equ $-DogsName
我首先考虑我可以单独移动字节,即首先是一个字节,然后是一个单词,或它们的某种组合。但是,我不能引用
eax
、rax
的“上半部分”,所以这在第一个障碍时就失败了,因为我最终会重写首先移动的任何数据。
我的解决方案:
26 mov al, byte [DogsName + 2] ; move the character “p” to register al
27 shl rax, 16 ; shift bits left by 16, clearing ax to receive characters “pi”
28 mov ax, word [DogsName] ; move the characters “Pi” to register ax
我可以将“Pip”声明为初始化数据项,但示例只是一个示例,我想了解如何在汇编中引用24位,或40、48...就此而言。
是否有更类似于
MOV r24,[m24]
的指令?是否有办法选择一系列内存地址,而不是提供偏移量和指定大小运算符。如何将3个字节从内存移动到ASMx86_64中的注册?
NASM版本2.11.08架构x86
使用BMI2,您可以使用BZHI
BZHI r32a, r/m32, r32b Zero bits in r/m32 starting with the position in r32b, write result to r32a
BZHI r64a, r/m64, r64b Zero bits in r/m64 starting with the position in r64b, write result to r64a
因此,要从mem加载低24位,可以使用
MOV eax, 24
BZHI eax, [mem], eax
有了这个,您还可以从内存中加载可变数量的位
写入24位的唯一方法是使用MMX(MASKMOVQ
)或SSE(MASMODQU
)和掩码来防止您不想修改的字节被修改。但是,对于单次写入,MMX和SSE过于复杂(并且可能更慢)。
请注意,通常读取比写入便宜(尤其是在涉及多个CPU时)。考虑到这一点,另一种选择是:
shl eax,8
mov al,[DogsName+3]
ror eax,8
mov [DogsName],eax
这将使用其旧值覆盖后字节(如果后字节无法访问,或者后字节属于需要原子更新的任何内容,则可能会导致问题)。
匿名用户
如果您知道3字节int不在页面的末尾,通常您会进行4字节加载并屏蔽所需字节附带的高垃圾,或者如果您正在处理不关心高位的数据,则直接忽略它。如果只需要结果的低部分,在不将输入中的高位置零的情况下,可以使用哪个2的补码整数运算?
与stores不同,加载“不应该”的数据从来都不是正确性的问题,除非您进入一个未映射的页面。(例如,如果db“pip”出现在页面的末尾,并且下一个页面未映射。)但在这种情况下,您知道它是较长字符串的一部分,因此,如果较宽的负载扩展到下一个缓存线(因此负载跨越缓存线边界),则唯一可能的缺点是性能。在x86和x64上读取同一页中的缓冲区是否安全?
对于任何3个字节,之前的字节或之后的字节都可以安全访问(如果3个字节本身没有在两个缓存行之间拆分,甚至不会跨越缓存行边界)。在运行时弄清楚这一点可能不值得,但如果您在编译时知道对齐,您可以这样做
mov eax, [DogsName-1] ; if previous byte is in the same page/cache line
shr eax, 8
mov eax, [DogsName] ; if following byte is in the same page/cache line
and eax, 0x00FFFFFF
我假设您希望将结果零扩展到eax/rax,如32位操作数大小,而不是与eax/rax的现有高字节合并,如8或16位操作数大小的寄存器写入。如果确实要合并,请屏蔽旧值并或。或者,如果您从DogsName-1加载,那么您想要的字节位于EAX的前3个位置,并且您想要合并到ECX中:shr-ECX,24/shld-ECX,EAX,24将旧的顶部字节下移到底部,然后在移动3个新字节的同时将其向后移动。(遗憾的是,shld没有内存源形式。半相关:有效地从两个单独的DWORD加载到一个qword中。)shld在Intel CPU(尤其是Sandybridge和更高版本:1 uop)上速度很快,但在AMD上不快(http://agner.org/optimize/).
有很多方法可以做到这一点,但不幸的是,在所有CPU中没有一种最快的方法。部分寄存器写入在不同的CPU上表现不同。在除Core2/Nehalem之外的其他CPU上,您的方式(字节加载/移位/字加载到ax中)相当不错(在组装后读取eax时,它会暂停插入合并uop)。但是从movzx eax开始,字节[DogsName 2]
打破对旧值rax的依赖。
您希望编译器生成的经典“处处安全”代码是:
DEFAULT REL ; compilers use RIP-relative addressing for static data; you should too.
movzx eax, byte [DogsName + 2] ; avoid false dependency on old EAX
movzx ecx, word [DogsName]
shl eax, 16
or eax, ecx
这需要额外的指令,但避免写入任何部分寄存器。然而,在Core2或Nehalem以外的CPU上,2个负载的最佳选择是写入ax。(Core2之前的Intel P6无法运行x86-64代码,没有部分寄存器重命名的CPU在写入ax时将合并到rax中)。Sandybridge仍然重命名AX,但合并只需要1个uop,没有暂停,即与OR相同,但在Core2/Nehalem上,插入合并uop时前端暂停约3个周期。
Ivybridge和更高版本仅重命名为AH,而不是AX或AL,因此在这些CPU上,AX中的负载是微融合负载合并。Agner Fog没有列出Silvermont或Ryzen(或我查看的电子表格中的任何其他选项卡)上的mov r16,m的额外惩罚,因此可能其他没有部分reg重命名的CPU也会执行mov ax,[mem]
作为负载合并。
movzx eax, byte [DogsName + 2]
shl eax, 16
mov ax, word [DogsName]
; when read eax:
; * Sandybridge: extra 1 uop inserted to merge
; * core2 / nehalem: ~3 cycle stall (unless you don't use it until after the load retires)
; * everything else (including IvB+): no penalty, merge already done
实际上,可以高效地在运行时测试对齐。给定寄存器中的指针,前一个字节位于同一缓存线中,除非地址的最后5位或6位都为零。(即,地址与缓存线的开头对齐)。假设缓存线是64字节;所有当前的CPU都使用它,我认为没有任何具有32字节行的x86-64 CPU存在。(我们仍然明确避免跨页)。
; pointer to m24 in RSI
; result: EAX = zero_extend(m24)
test sil, 111111b ; test all 6 low bits. There's no TEST r32, imm8, so REX r8, imm8 is shorter and never slower.
jz .aligned_by_64
mov eax, [rsi-1]
shr eax, 8
.loaded:
...
ret ; end of whatever large function this is part of
; unlikely block placed out-of-line to keep the common case fast
.aligned_by_64:
mov eax, [rsi]
and eax, 0x00FFFFFF
jmp .loaded
因此,在常见情况下,额外的成本只是一个未进行测试和分支的uop。
根据CPU、输入和周围的代码,测试低12位(仅避免跨越4k边界)将权衡页面内某些缓存行拆分的更好分支预测,但仍然不会进行页面行拆分。(在这种情况下测试esi,(1
您甚至可以无分支地执行此操作,但显然不值得这样做,而只需执行两个单独的加载!
; pointer to m24 in RSI
; result: EAX = zero_extend(m24)
xor ecx, ecx
test sil, 7 ; might as well keep it within a qword if we're not branching
setnz cl ; ecx = (not_start_of_line) ? : 1 : 0
sub rsi, rcx ; normally rsi-1
mov eax, [rsi]
shl ecx, 3 ; cl = 8 : 0
shr eax, cl ; eax >>= 8 : eax >>= 0
; with BMI2: shrx eax, [rsi], ecx is more efficient
and eax, 0x00FFFFFF ; mask off to handle the case where we didn't shift.
架构上,x86没有24位加载或存储,以整数寄存器作为目标或源。正如Brandon指出的那样,MMX/SSE掩码存储(如MASKMOVDQU,不要与PMOVMkb eax、xmm0混淆)可以存储MMX或XMM reg中的24位,给定仅设置了低3字节的向量掩码。但是它们几乎没有什么用处,因为它们速度慢,并且总是有NT提示(因此它们在缓存周围写,并强制逐出,如movntdq)。(AVX dword/Q字掩码加载/存储指令并不意味着NT,但不适用于字节粒度。)
AVX512BW(Skylake服务器)添加了vmovdqu8,它为加载提供字节屏蔽,并对屏蔽的字节进行故障抑制存储。(即,如果16字节加载包括未映射页面中的字节,只要没有为该字节设置掩码位,就不会进行分段故障。但这确实会导致速度大幅放缓)。因此,微体系结构上,它仍然是一个16字节的加载,但对体系结构状态(即除性能外的所有内容)的影响与真正的3字节加载/存储(具有正确的掩码)完全相同。
您可以在XMM、YMM或ZMM寄存器上使用它。
;; probably slower than the integer way, especially if you don't actually want the result in a vector
mov eax, 7 ; low 3 bits set
kmovw k1, eax ; hoist the mask setup out of a loop
; load: leave out the {z} to merge into the old xmm0 (or ymm0 / zmm0)
vmovdqu8 xmm0{k1}{z}, [rsi] ; {z}ero-masked 16-byte load into xmm0 (with fault-suppression)
vmovd eax, xmm0
; store
vmovd xmm0, eax
vmovdqu8 [rsi]{k1}, xmm0 ; merge-masked 16-byte store (with fault-suppression)
如果您的NASM足够新,可以支持AVX512,则可以使用NASM 2.13.01.IDK进行组装。您可以使用Intel的软件开发模拟器(SDE)在没有硬件的情况下使用AVX512
这看起来很酷,因为只需2个UOP即可将结果输入eax(一旦设置了掩码)。(然而,http://instlatx64.atw.hu/来自IACA的Skylake-X数据电子表格不包括带掩码的vmovdqu8,只包括未屏蔽的表单。这些确实表明它仍然是一个单uop负载,或微熔合存储,就像一个常规的vmovdqu/a)
但是,如果16字节负载出现故障或跨越缓存线边界,请注意速度减慢。我认为它会在内部进行加载,然后丢弃字节,如果需要抑制故障,则这是一种潜在的昂贵的特殊情况。
此外,对于存储版本,请注意屏蔽存储不会有效地转发到加载。(有关更多信息,请参阅英特尔的优化手册)。
脚注:
宽存储是一个问题,因为即使您替换旧值,您也会执行非原子读取-修改-写入,例如,如果您放回的字节是锁,这可能会破坏东西。除非您知道接下来会发生什么并且它是安全的,否则不要存储在对象之外,例如您放置在那里以允许这样做的填充。您可以将cmpxchg
修改后的4字节值锁定到位,以确保您没有踩到另一个线程对额外字节的更新,但显然进行2个单独的存储比原子cmpxchg
重试循环的性能要好得多。
为什么我不能在英特尔x86-64汇编中直接将一个字节从内存移到64位寄存器? 例如,此代码: 印刷品: 为了使代码正常工作,我需要将寄存器r12和r13的移动字节更改为: 现在,它打印出预期内容: 我们为什么要这样做? 有更简单的方法吗? 谢谢
本文向大家介绍verilog 移位寄存器,包括了verilog 移位寄存器的使用技巧和注意事项,需要的朋友参考一下 示例 具有异步复位功能的N位深移位寄存器。
我正在使用x86 Assembly,我遇到了使用指令的需要。 我的代码片段过去是这样的: 其中<代码> 所以我的程序正在命中未定义的行为,在这种情况下是一个无限循环,在检查了很长时间可能出错的地方之后,我认为我必须使用,因为 实际上将结果存储在< code>ax中。因此,我在< code>ax中有一个有符号的结果,而我希望在< code>al中有一个无符号的(有上溢/下溢)结果。 所以我的问题是:
问题内容: 对于我的10,000点,我决定在这个很酷的网站上做出一些贡献:一种将位图缓存在本机内存中的机制。 背景 Android设备为每个应用程序分配的内存非常有限-堆的范围从16MB到128MB,具体取决于各种参数。 如果超过此限制,则会得到OOM,并且在使用位图时可能会发生多次。 很多时候,应用可能需要克服这些限制,对巨大的位图执行繁重的操作,或者只是将其存储以备后用,而您需要 我想出的是一
我正在寻找一种方法来移动arch64寄存器x1中的任何32位常数。 是否有一种方法可以执行
基本上指令有8->16、8->32、8->64、16->32和16->64。 32->64的转换在哪里?我必须使用签名版本吗? 如果是的话,您如何使用完整的64位来表示无符号整数?