热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MOVZX缺少32位寄存器到64位寄存器

如何解决《MOVZX缺少32位寄存器到64位寄存器》经验,为你挑选了1个好方法。

这是复制(转换)未签名寄存器的指令:http : //www.felixcloutier.com/x86/MOVZX.html

基本上,该指令具有8-> 16、8-> 32、8-> 64、16-> 32和16-> 64。

32-> 64转换在哪里?我需要为此使用签名版本吗?
如果是这样,如何将全64位用于无符号整数?



1> Peter Cordes..:

简短答案

使用mov eax, edi以零扩展到EDI RAX如果您已经不能保证RDI的高位均为零。请参阅:为什么32位寄存器上的x86-64指令将整个64位寄存器的上半部分归零?

最好使用不同的源/目标寄存器,因为在Intel和AMD CPU上,消除运动均失败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个操作码。英特尔将它们全部记录在一份ISA参考手册中。他们改用了ARPL32位模式下的1字节操作码,因此movsxd实际上比movsx8或16位源短1字节(假设您仍需要REX前缀才能扩展到64位)。

不同的目标大小使用具有不同操作数大小1的相同操作码。(66REX.W16位或64位前缀,而不是默认的32位前缀。)例如,movsx eax, bl并且movsx rax, bl仅REX前缀有所不同;相同的操作码。(movsx ax, bl也相同,但前缀为66以使操作数大小为16位。)

在AMD64之前,不需要读取32位源的操作码,因为最大目标宽度为32位,并且“符号扩展”到相同大小只是一个副本。注意这movsxd eax, eax是合法的,但不建议这样做。您甚至可以使用66前缀对其进行编码,以读取32位源并写入16位目标2

不建议在64位模式下使用不带REX.W的MOVSXD。应该使用常规的MOV代替没有REX.W的MOVSXD。

可以使用32-> 64位符号扩展来cdq将EAX符号扩展到EDX:EAX(例如32位之前idiv)。这是x86-64之前的唯一方法(当然,除了复制和使用算术右移之外,还可以广播符号位)。


但是AMD64已经可以通过任何写入32位寄存器的指令将32从64扩展为64。 这样可以避免错误的依存关系,从而无序执行,这就是为什么AMD打破了8086/386的传统,即在写入部分寄存器时不影响高位字节的传统。(为什么GCC不使用部分寄存器?)

由于每个源宽度需要不同的操作码,因此没有前缀可以使两个movzx操作码中的任何一个读取32位源


有时您确实需要花费一条指令对某些内容进行零扩展。在小型函数的编译器输出中很常见,因为x86-64 SysV和Windows x64调用约定在args和返回值中允许大量垃圾。

像往常一样,询问编译器是否想知道如何在asm中执行某些操作,尤其是在没有看到所需指令的情况下。我ret在每个函数的末尾都省略了。

来自Godbolt编译器浏览器的Source + asm,用于System V调用约定(RDI,RSI,RDX等中的参数)

#include 

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]

在x86-64中,默认地址大小为64。高垃圾不会影响加法的低位,因此这节省了一个字节,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占用的微体系结构资源,并提高- mov消除的成功率(与AMD Ryzen不同,在Sandybridge系列中并非100%)。GCC对mov/ 的选择imul是最好的。

同样,在没有消除运动的CPU mov上,如果其他输入尚未准备好,则before imul可能不在关键路径上(即,如果关键路径通过了未得到moved 的输入)。但是mov之后imul取决于两个输入,因此始终处于关键路径上。

当然,当这些函数内联时,除非它们来自函数返回值,否则编译器通常将知道寄存器的完整状态。并且它也不需要在特定的寄存器中产生结果(RAX返回值)。但是,如果您的源代码unsignedsize_t或混合使用时过于草率uint64_t,则可能会迫使编译器发出指令以截断64位值。(查看编译器的asm输出是捕获该错误并弄清楚如何调整源代码以使编译器保存指令的好方法。)


脚注1:有趣的事实:AT&T语法(使用movswl(sign-extend word-> long(dword)或movzbl)等不同的助记符)可以从寄存器中推断出目标大小,例如movzb %al, %ecx,即使没有歧义也不会汇编movz %al, %ecx。将movzb其视为自己的助记符,并具有通常的操作数大小后缀(可以推断或显式),这意味着每个不同的操作码在AT&T语法中都有其自己的助记符。

有关EAX-> RAX的CDQE和任何寄存器的MOVSXD之间的冗余的历史课程,另请参见Assembly cltq和movslq差异。请参阅cltq在组装中做什么?或针对AT&T与Intel规范的GAS文档(零扩展/符号扩展)。

脚注2:愚蠢的计算机技巧movsxd ax, [rsi]

汇编程序拒绝汇编movsxd eax, eaxmovsxd 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

那么,CPU如何在内部对其进行处理?它是否实际读取32位,然后截断为操作数大小? 事实证明,Intel的ISA参考手册将16位格式记录为63 /r MOVSXD r16, r/m16,所以movsxd ax, [unmapped_page - 2]不会出错。 (但是它错误地记录了非REX格式在兼容/旧版模式下有效;实际上0x63在此处解码为ARPL。这不是英特尔手册中的第一个错误。)

这非常有道理:硬件可以在简单地解码相同的UOP mov r16, r/m16mov r32, r/m32当没有REX.W前缀。或不! Skylake的movsxd eax,edx(但不是movsxd rax, edx)对目标寄存器具有输出依赖性,就像它正在合并到目标中一样! 一个循环,times 4 db 0x63, 0xc2 ; movsx eax, edx每个迭代以4个时钟运行(每个循环1个movsxd,因此1个周期延迟)。微指令相当均匀地分布到所有4个整数ALU执行端口。带有movsxd eax,edx// movsxd ebx,edx2个其他目标的循环在每次迭代中以〜1.4个时钟运行(如果使用普通4x mov eax, edx或4x,则略小于每次迭代前端瓶颈1.25个时钟movsxd rax, edx)。具有定时perf在Linux上i7-6700k。

我们知道这样movsxd eax, edx做会将RAX的高位清零,因此实际上并没有使用它正在等待的目标寄存器中的任何位,而是大概在内部对16位和32位进行了类似的处理,从而简化了解码,并且简化了这种极端情况下编码的处理,任何人都不应该这样做曾经使用过。16位格式始终必须实际合并到目标中,因此它确实对输出reg有真正的依赖性。(Skylake不会将全名寄存器单独重命名16位reg。)

GNU binutils错误地分解了它:gdb和objdump将源操作数显示为32位,例如

  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]

在AT&T语法中,objdump仍然有趣地使用movslq。因此,我想它会将其视为一个整体助记符,而不是将其视为movsl具有q操作数大小的指令。或这仅仅是因为没人关心气体无论如何都不会聚集的特殊情况(它拒绝movsll,并检查寄存器宽度是否为movslq)。

在检查手册之前,我实际上在NASM上的Skylake上进行了测试,以查看负载是否会发生故障。它当然不会:

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)之间选择。 movs/zx ax, word [mem]从386开始就可以使用,但是读取比目标更宽的源是x86-64中新增的特殊情况,并且仅用于符号扩展。(事实证明,16位目标编码实际上仅读取16位源。)


AMD选择不做的其他ISA设计可能性:

顺便说一句,AMD 可以(但不是)将AMD64设计为始终对32位寄存器写入进行符号扩展而不是始终为零扩展。在大多数情况下,这对于软件来说不太方便,并且可能还需要使用一些额外的晶体管,但是仍然可以避免错误地依赖寄存器中的旧值。这可能会在某处增加额外的门控延迟,因为结果的高位取决于低位,与零扩展不同,零扩展只取决于它是32位运算的事实。(但这可能并不重要。)

如果 AMD设计了这样的说法,他们就会需要一个movzxd 代替movsxd。我认为,将位域打包到更宽的寄存器中时,此设计的主要缺点是需要额外的说明。例如,在写入and 之后的shl rax,32/ or rax, rdx之后rdtsc,免费零扩展名很方便。如果是符号扩展名,则需要一条指令将之前的高字节清零。edxeaxrdxor


其他ISA也做出了不同的选择:MIPS III(在1995年左右)将体系结构扩展到64位,而没有引入新的模式。与x86非常不同,在固定宽度的32位指令字格式中,有足够的操作码空间未使用。

MIPS最初是一种32位体系结构,从没有16位8086传统和32位x86完全支持32位x86的传统遗留部分寄存器内容,而AX = AH :AL部分规则等,可轻松移植8080源代码。

MIPS 32位算术指令(例如addu在64位CPU上)要求其输入正确进行符号扩展,并产生符号扩展的输出。 (一切都只是工作运行传统的32位代码,不知道广大寄存器的时候,因为转移是特殊的。)

ADDU rd, rs, rt(摘自MIPS III手册,第A-31页)

限制:
在64位处理器上,如果GPR rt或GPR rs不包含符号扩展的32位值(位63..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针对双字ADDU的说明,它可以满足您的期望。类似地,DDIV / DMULT / DSUBU和DSLL等移位。

按位运算保持不变:现有的AND操作码变为64位AND;不需要64位AND,也不需要32位AND结果的免费符号扩展。

MIPS 32位移位是特殊的(SLL是32位移位。DSLL是单独的指令)。

SLL移位字左逻辑

操作方式:

s ? sa
temp ? GPR[rt] (31-s)..0 || 0 s
GPR[rd]? sign_extend(temp)

编程说明:
与几乎所有其他字操作不同,输入操作数不必是正确的符号扩展字值即可产生有效的符号扩展32位结果。结果字始终被符号扩展到64位目标寄存器中。该移位量为零的指令将64位值截断为32位,并对其进行符号扩展。

我认为SPARC64和PowerPC64在保持窄结果的符号扩展方面类似于MIPS64。 代码生成用于(a & 0x80000000) +- 12315int a(与-fwrapv这样编译器不能假定a非负的,因为签署溢出UB)的节目铿锵的PowerPC64维持或重拾符号扩展,和铛-target sparc64安定然后或运算,以确保在低温只有合适的位设置32,再次保持符号扩展。将返回类型或arg类型更改为AND掩码常量longL在AND掩码常量上添加后缀会导致MIPS64和PowerPC64,有时甚至是SPARC64的代码有所不同。也许只有MIPS64实际上会在输入没有正确符号扩展的32位指令上出错,而​​在其他情况下,这仅仅是软件调用约定要求。

但是AArch64采用的方法更类似于x86-64,w0..31寄存器是的下半部分x0..31,并且指令有两种操作数大小。

关于MIPS的整个部分与x86-64无关,但是,比较AMD64做出的不同(更好的IMO)设计决策,这是一个有趣的比较。

对于这些示例函数,我在上面的Godbolt链接中包括了MIPS64编译器输出。(还有一些其他的东西可以告诉我们更多有关调用约定以及什么是编译器的信息。)它通常需要dext从32位零扩展到64位。但是直到mips64r2才添加该指令。使用-march=mips3return p[a]因为无符号a必须使用两个双字移位(左移然后右移32位)以零扩展!它还需要一条额外的指令来对添加结果进行零扩展,即实现从无符号到的转换uint64_t

因此,我认为我们很高兴x86-64设计为具有免费的零扩展名,而不是仅为某些事情提供64位操作数大小。(就像我说的那样,x86的传统非常不同;对于使用前缀的相同操作码,它已经具有可变的操作数大小。)当然,更好的位域指令会更好。其他一些ISA(例如ARM和PowerPC)使x86显得羞愧,无法有效地进行位域插入/提取。


推荐阅读
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • PHP引用的概念和用法详解
    本文详细介绍了PHP中引用的概念和用法。引用是指不同的变量名访问同一个变量内容,类似于Unix文件系统中的hardlink。文章从引用的定义、作用、语法和注意事项等方面进行了解释和示例。同时还介绍了对未定义变量使用引用的情况,以及在函数和new运算符中使用引用的注意事项。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
author-avatar
xinyaolin_857
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有