- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
这是复制(转换)无符号寄存器的指令:http://www.felixcloutier.com/x86/MOVZX.html
基本上,该指令具有8-> 16、8-> 32、8-> 64、16-> 32和16-> 64。
32-> 64转换在哪里?我必须使用签名版本吗?
如果是这样,如何将全64位用于无符号整数?
最佳答案
简短答案
如果您不能保证RDI的高位全部为零,请使用mov eax, edi
将EDI零扩展到RAX。参见:Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?
最好使用不同的源/目标寄存器,因为在Intel和AMD CPU上均使用mov-elimination fails for mov eax,eax
。当转移到另一个寄存器时,不需要任何执行单元就可以实现零延迟。 (gcc显然不知道这一点,通常零扩展。)但是,不要花费额外的指令来实现这一目标。
长答案
使用32位源的movzx没有编码的机器代码原因
摘要:movzx和movsx的每个不同的源宽度都需要不同的操作码。目标宽度由前缀控制。由于mov
可以完成这项工作,因此movzx dst, r/m32
的新操作码将是多余的。
在设计AMD64汇编器语法时,AMD选择不使movzx rax, edx
作为mov eax, edx
的伪指令。这可能是一件好事,因为知道编写32位寄存器会将高字节清零对于为x86-64编写高效代码非常重要。
AMD64确实需要使用32位源操作数进行符号扩展的新操作码。他们出于某种原因将助记符命名为movsxd
,而不是将其作为movsx
助记符的第3个操作码。英特尔将它们全部记录在one ISA ref manual entry中。他们重新调整了32位模式下ARPL
的1字节操作码的用途,因此实际上movsxd
比8或16位源中的movsx
短1字节(假设您仍然需要REX前缀以扩展到64)位)。
不同的目标大小将相同的操作码用于不同的操作数大小1。 (66
或REX.W
前缀为16位或64位,而不是默认的32位。)例如movsx eax, bl
和movsx rax, bl
仅REX前缀有所不同;相同的操作码。 (movsx ax, bl
也相同,但是前缀为66,以使操作数大小为16位。)
在AMD64之前,不需要读取32位源的操作码,因为最大目标宽度是32位,并且“符号扩展”到相同大小只是一个副本。请注意,movsxd eax, eax
is legal but not recommended。您甚至可以使用66
前缀对其进行编码,以读取32位源并写入16位destination2。
不建议在64位模式下使用不带REX.W的MOVSXD。应该使用常规的MOV代替没有REX.W的MOVSXD。
可以使用cdq
完成32-> 64位符号扩展,以将EAX符号扩展到EDX:EAX(例如32位idiv
之前)。这是x86-64之前的唯一方法(当然,除了复制和使用算术右移之外,还必须广播符号位)。
但是,AMD64已经可以通过任何写入32位寄存器的指令免费将32从零扩展到64。 This avoids false dependencies用于乱序执行,这就是为什么AMD打破了8086/386的传统,即在编写部分寄存器时不影响高位字节的传统。 (Why doesn't GCC use partial registers?)
由于每个源宽度需要不同的操作码,因此没有任何前缀可以使两个movzx
操作码中的任何一个读取32位源。
有时您确实需要花费一条指令对某些内容进行零扩展。在小型函数的编译器输出中很常见,因为x86-64 SysV和Windows x64调用约定允许args和返回值中有大量垃圾。
像往常一样,询问编译器是否想知道如何在asm中执行某些操作,尤其是在没有看到所需指令的情况下。我在每个函数末尾都省略了ret
。
Source + asm from the Godbolt compiler explorer, for the System V calling convention (args in RDI, RSI, RDX, ...):
#include <stdint.h>
uint64_t zext(uint32_t a) { return a; }
uint64_t extract_low(uint64_t a) { return a & 0xFFFFFFFF; }
# both compile to
mov eax, edi
int use_as_index(int *p, unsigned a) { return p[a]; }
# gcc
mov esi, esi # missed optimization: mov same,same can't be eliminated on Intel
mov eax, DWORD PTR [rdi+rsi*4]
# clang
mov eax, esi # with signed int a, we'd get movsxd
mov eax, dword ptr [rdi + 4*rax]
uint64_t zext_load(uint32_t *p) { return *p; }
mov eax, DWORD PTR [rdi]
uint64_t zext_add_result(unsigned a, unsigned b) { return a+b; }
lea eax, [rdi+rsi]
lea eax, [edi+esi]
相比,它节省了一个字节,该字节需要67个地址大小的前缀,但每个输入都得到相同的结果。当然,
add edi, esi
将在RDI中产生零扩展的结果。
uint64_t zext_mul_result(unsigned a, unsigned b) { return a*b; }
# gcc8.1
mov eax, edi
imul eax, esi
# clang6.0
imul edi, esi
mov rax, rdi # silly: mov eax,edi would save a byte here
mov
的结果,释放
mov
消除所占用的微体系结构资源,并释放
increasing the success-rate of mov
-elimination (which isn't 100% on Sandybridge-family, unlike AMD Ryzen)。 GCC选择
mov
/
imul
是最好的。
mov
可能不在关键路径上(即,如果关键路径经过了未得到
mov
的输入) )。但是
mov
之后的
imul
取决于两个输入,因此它始终位于关键路径上。
unsigned
与
size_t
或
uint64_t
混合使用时,源代码很草率,则可能会迫使编译器发出指令以截断64位值。 (查看编译器的asm输出是捕获该错误并弄清楚如何调整源代码以使编译器保存指令的好方法。)
movswl
(符号扩展字-> long(dword)或
movzbl
等不同的助记符))可以从寄存器中推断出目标大小,例如
movzb %al, %ecx
,但是
won't assemble movz %al, %ecx
even though there's no ambiguity因此,它把
movzb
当作自己的助记符,通常的操作数大小后缀可以推断或显式表示,这意味着每个不同的操作码在AT&T语法中都有自己的助记符。
movsxd ax, [rsi]
的愚蠢计算机技巧:
movsxd eax, eax
或
movsxd ax, eax
,但是可以对其进行手动编码。
ndisasm
甚至都没有反汇编(只是
db 0x63
),但是GNU
objdump
可以反汇编。实际的CPU也会对其进行解码。我尝试在Skylake上只是为了确保:
; NASM source ; register value after stepi in GDB
mov rdx, 0x8081828384858687
movsxd rax, edx ; RAX = 0xffffffff84858687
db 0x63, 0xc2 ;movsxd eax, edx ; RAX = 0x0000000084858687
xor eax,eax ; RAX = 0
db 0x66, 0x63, 0xc2 ;movsxd ax, edx ; RAX = 0x0000000000008687
63 /r
MOVSXD r16, r/m16
,因此
movsxd ax, [unmapped_page - 2]
不会出错。 (但是它错误地记录了非REX格式在兼容/旧版模式下有效;实际上
0x63
在那里被解码为ARPL。这不是Intel手册中的第一个错误。)
mov r16, r/m16
或
mov r32, r/m32
相同的uop。或不!
movsxd eax,edx
(但不是
movsxd rax, edx
)对目标寄存器具有输出依赖性,就像它正在合并到目标中一样!包含
times 4
db 0x63, 0xc2 ; movsx eax, edx
的循环在每次迭代中以4个时钟运行(每个
movsxd
1个循环,因此有1个周期延迟)。微指令相当均匀地分布到所有4个整数ALU执行端口。具有
movsxd eax,edx
/
movsxd ebx,edx
/ 2个其他目标的循环在每次迭代中以〜1.4个时钟运行(如果使用普通的4x
mov eax, edx
或4x
movsxd rax, edx
,则略小于每个迭代前端瓶颈1.25个时钟)。在i7-6700k的Linux上使用
perf
计时。
movsxd eax, edx
会将RAX的高位清零,因此实际上并没有使用它正在等待的目标寄存器中的任何位,而是大概在内部对16位和32位进行了类似的处理,从而简化了解码,并简化了这种特殊情况编码的处理没有人应该使用。 16位格式始终必须实际上合并到目标中,因此它确实对输出reg有真正的依赖性。 (Skylake不会将全名寄存器单独重命名16位reg。)
4000c8: 66 63 c2 movsxd ax,edx
4000cb: 66 63 06 movsxd ax,DWORD PTR [rsi]
4000c8: 66 63 c2 movsxd ax,dx
4000cb: 66 63 06 movsxd ax,WORD PTR [rsi]
movslq
。因此,我想它会将其视为一个整体助记符,而不是具有
movsl
操作数大小的
q
指令。或这仅仅是因为没人关心气体不会聚集的特殊情况(它拒绝
movsll
并检查
movslq
的寄存器宽度)的结果。
section .bss
align 4096
resb 4096
unmapped_page:
; When built into a static executable, this page is followed by an unmapped page on my system,
; so I didn't have to do anything more complicated like call mmap
...
_start:
lea rsi, [unmapped_page-2]
db 0x66, 0x63, 0x06 ;movsxd ax, [rsi]. Runs without faulting on Skylake! Hardware only does a 2-byte load
o16 movsxd rax, dword [rsi] ; REX.W prefix takes precedence over o16 (0x66 prefix); this faults
mov eax, [rsi] ; definitely faults if [rsi+2] isn't readable
movsx al, ax
是不可能的:字节操作数大小需要单独的操作码。前缀仅在32(默认),16位(0x66)和长模式64位(REX.W)之间选择。从386开始,就可以使用
movs/zx ax, word [mem]
了,但是读取比目标更宽的源代码是x86-64中的一个特例,仅用于符号扩展。 (事实证明,16位目标编码实际上仅读取16位源。)
movzxd
而不是
movsxd
。我认为,将位域打包到更宽的寄存器中时,此设计的主要缺点是需要额外的说明。例如,在写有
shl rax,32
和
or rax, rdx
的
rdtsc
之后,
edx
/
eax
的自由零扩展很方便。如果是符号扩展名,则需要一条指令将
rdx
之前的
or
高字节清零。
addu
)要求其输入正确进行符号扩展,并产生符号扩展的输出。 (由于运行移位是特殊的,因此在运行传统的32位代码而没有意识到更宽的寄存器时,所有方法都适用。)
ADDU rd, rs, rt
(
from the MIPS III manual, page A-31)
if (NotWordValue(GPR[rs]) or NotWordValue(GPR[rt])) then UndefinedResult() endif
temp ←GPR[rs] + GPR[rt]
GPR[rd]← sign_extend(temp31..0)
addu
中无符号的U确实是一个错误的名词。除非确实希望
add
捕获有符号的溢出,否则也将其用于有符号的算术。)
DADDU
指令,它可以完成您所期望的。类似地,DDIV / DMULT / DSUBU和DSLL等移位。
s ← sa
temp ← GPR[rt] (31-s)..0 || 0 s
GPR[rd]← sign_extend(temp)
(a & 0x80000000) +- 12315
for int a
(使用
-fwrapv
(因此,编译器不能假定
a
是非负的,因为有符号溢出的UB))显示了用于PowerPC64的clang维护或重做符号扩展,然后对ccc
-target sparc64
进行“与”运算,然后进行“或”运算以确保只有低位32的右位被置1,再次保持符号扩展。将返回类型或arg类型更改为
long
或在AND掩码常量上添加
L
后缀会导致MIPS64和PowerPC64,有时甚至是SPARC64的代码有所不同。也许只有MIPS64实际上会在输入未正确符号扩展的32位指令上出错,而在其他情况下,这仅仅是软件调用约定要求。
w0..31
寄存器是
x0..31
的下半部分,并且指令有两种操作数大小。
dext
来从32位零扩展到64位。但是直到mips64r2才添加该指令。对于
-march=mips3
,用于无符号
return p[a]
的
a
必须使用两个双字移位(左移然后右移32位)以零扩展!它还需要一条额外的指令来对添加结果进行零扩展,即实现从无符号到
uint64_t
的转换。
关于assembly - MOVZX缺少32位寄存器到64位寄存器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51387571/
我被告知“汇编”是您在文件中编写的内容,让您的“汇编程序”将其转换为二进制代码。 但我看到这两个术语在各种作品中混合搭配。我什至听说你编写了“汇编器”,然后“汇编器”使其可执行。 正确的用词是什么?
我在正确终止用 Assembly 编写的 16 位 DOS 程序时遇到问题。这是部分代码: .386P .model flat stack_s segment stack 'stack'
我需要多少档才能正确执行以下指令。我对我所做的事情有些困惑,所以我在这里看到专家的答案。 lw $1,0($2); beq $1,$2,Label; 请注意,检查是否会发生分支将在解码阶段完成。但是在
我正在尝试在汇编中进行简单的乘法运算,但是由于某些原因,当标记了MUL函数时,我看不到寄存器会发生变化。 mov bx, 5 mov cx, 10 mul cx 最佳答案 这些称为指令,它们指定
我正在尝试在 Assembly 中实现递归斐波那契程序。但是,我的程序崩溃了,出现了未处理的异常,我似乎无法找出问题所在。我不怀疑这涉及我对堆栈的不当使用,但我似乎无法指出哪里...... .386
我编写了以下代码: .386 .model small .stack 100h .data text db "Paper",0 .code start : lea dx ,
我有一个用汇编语言编写的裸机 ARM 的启动代码,我正在尝试了解它是如何工作的。该二进制文件被写入一些外部闪存中,并在启动时将其自身的一部分复制到 RAM 中。尽管我读过这篇文章wikipedia e
我在数据部分定义了一个二维数组和两个一维数组(一个用于列总和,一个用于行总和),并且我编写了一个函数,将二维数组求和到一维数组中。我使用 eax 和 ebx 作为二维数组的索引,但是当 eax 或 e
我正在开始组装,我正在使用 nasm 来组装代码,我正在尝试处理驻留在内存中的字符串并更改它,我想检查一个字节是否在某个范围内(ascii),这样我就可以决定如何处理它,我似乎不知道如何检查一个值是否
虽然您通常不希望将一个整体程序集用于小型项目以外的任何事情,但可能会将事物分离得太多。 组装分离过多的迹象/气味是什么? 最佳答案 第一个(明显的)是:在一个有很多项目的解决方案中,其中只有少数(比如
我正在尝试编写斐波那契的汇编代码版本,它给出第 n 个斐波那契数并返回它。 出于某种原因,它在存储斐波那契数的返回值和添加它们时遇到问题。 我希望它打印第 n 个斐波那契数。 我对我的代码做了一些修改
我有一个最小的、可重现的示例有两个问题,该示例具有三个针对 .NET Core 3.1 的项目。但我也想以 .NET Standard 2.0 为目标。 该示例适用于需要在运行时加载程序集并使用提供的
: 运算符在汇编中做什么?代码如下:DS:DX我还没有找到该运算符(operator)的任何文档。(我正在使用 NASM) 最佳答案 那实际上只是一个寄存器分隔符,而不是运算符。这意味着使用 DX 寄
我在哪里可以找到为 gmp-5.0.0 编写的程序的汇编代码我正在使用 UBUNTU 和 G++ 编译器..编译代码的命令是“g++ test.cc -o outp -lgmp” 实际上我想知道在 1
我是组装新手,我有一个关于如何表示负数的问题 我有三个 DWORDS 变量,比如说: result DWORD 0 i DWORD 3 j DWORD 5 我想计算这个公式:result = i -
我想编写我的第一个汇编程序。我在论文上做了一些程序,但这是我第一次使用编译器。我正在使用 ideone .我的程序很简单, 翻译 A = 5 - A到 assembly NEG A ADD A, 5
程序集,masm 嘿,我写了宏来打印存储在 dane1 段中的 1 字节值。 我将值除以 16,然后将提醒推送到堆栈,直到值==0。然后我弹出提醒将它们转换为 ASCII 码,并打印它们。 有人可以看
我正在研究 nasm 的一个大学项目。唯一的问题是我无法生成 162 和 278 之间的偶数随机数。我尝试了很多算法,但似乎无法限制范围内的数字。 是否有一个小技巧或调整来获得所需的范围内的数字?目的
终于在无数次错误的漫长 session 之后,希望这是最后一个。 没有编译或运行时错误,只是一个逻辑错误。 编辑:(固定伪代码) 我的伪代码: first = 1; second = 1; thir
我知道在程序集r0中调用函数时,包含第一个参数,直到r3是第四个。我知道,当它超过四个时,将使用堆栈指针,但是我不太确定具体细节。 r0-r3仍然保持前四个,其余的进入堆栈吗?我正在看下面的程序集,试
我是一名优秀的程序员,十分优秀!