如何将15字节指令从内存转移到CPU?

问题描述 投票:0回答:2

假设我们使用的是x86-64机器,这意味着它的通用寄存器是64位长,它的数据总线一次可以处理64位,它的ALU可以处理最大64位数(对吗?)。

有一个简单的指令

MOV $5, %eax

通过64位数据总线将32位数字移入CPU寄存器。

我看过以下内容:

An x86-64 instruction may be at most 15 bytes in length.

我的问题是,如果数据总线最大64位,这怎么可能?它如何处理120位指令。 CPU是否以多个周期获取它?

我的第二个问题是,是否有更长的特殊寄存器来存储所有120位?

assembly x86-64 cpu-architecture cpu-registers instruction-set
2个回答
1
投票

指令编码

现代X86指令由以下内容构建:

  • 前缀(0,1,2,3,4)
  • VEX(0,2,3)
  • OPCODE(1)
  • ModR / M(1)
  • SIB(0,1)
  • DISP(0,1,2,4)
  • IMM(0,1,2,4)

A prefix is zero to four bytes:

第1组:LOCK或REP 第2组:分段(CS,SS,DS,ES,FS,GS-并非全部以64位提供)和分支提示(即更可能采用的分支?) 第3组:操作数大小(66H,对于某些指令是强制性的!) 第4组:地址大小

VEX

VEX适用于AVX扩展(主要是)

OPCODE

OPCODE是实际指令,如果不计算VEX和其他一些前缀/特殊字节(如着名的0F),则只有8位。 (在过去,这是访问x86协处理器的方法。)

ModR/M defines the mode

它告诉我们在本说明书中使用了哪种寄存器和/或存储器模式。某些说明不支持所有可用模式。

Scale, Index, Base

SIB是ModR / M的扩展。

Displacment

DISP是位移,立即添加到地址寄存器(如[ESP + 13])它也可以是存储位置的直接地址。

Immediate

IMM是一个直接值(在MOV EBX, $8中 - 8是加载在EBX中的值,即直接值。)

请注意,IMM通常限制为32位。 REX可用于获得64位,但它并不适用于所有指令(因为任何一条指令的总字节数为15字节)。要在寄存器中加载64位,始终从内存加载它。这样做的一种方法是使用基于IP的地址。 (像这样的东西:MOV R8, [RIP, -42])我也注意到过去像gcc这样的编译器没有使用那个指令。但是,对于64位处理器,可以使用32位位移,因此该值几乎可以达到任何值(±2Gb)。

加载说明

64位处理器在指令高速缓存中加载指令。它一次加载16个字节(可能因处理器而异)。然后处理器解释这些字节。根据处理器的不同,它可能会将这些字节转换为一组RISC指令或直接执行指令。

例如,LOOP label指令实际上至少相当于两条指令:

SUB ECX, 1
JNZ label

有些处理器过去很难用,所以LOOP很慢。一个原因是当SUB没有变化时,EFLAGS改变了许多LOOP

解释器不会在寄存器中加载指令。它将它加载到CPU中并在相应的单元(ALU,ACU,FPU等)中处理它。但是有一个RIP寄存器指向当前指令。就您而言,RIP始终指向当前指令的开头或下一条指令的开始。

它实际上是如何实现的,我不知道。他们可能很快(即时)确定涉及哪个单位并将指令推到那里。确定大小并不复杂,因此他们可以快速获取所有字节并将其推送到相关的单元FIFO,可能是15或16字节值(即FIFO中的一个项目肯定总是16个字节,一个字节可能被忽略,硬件甚至没有线来读取它!)这些字节每次都位于同一位置。因此,如果输入没有LOCKREP,则会将00h称为该FIFO字节。

请注意,在单元之间移动16个字节的FIFO是没有的。多年来,GPU在其FIFO中移动了大量数据。

你可以说这些FIFO是额外的寄存器。寄存器文件与FIFO相同,只是它具有随机访问而不是机制的“PUSH / POP”类型。两者都使用类似的技术,即a.k.a. memory,将数据保存在FIFO和寄存器中。

文档

我会建议第一份文件,目前标题为:

Intel® 64 and IA-32 architectures software developer’s manual combined volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4

从英特尔作为关于可用指令的一个很好的阅读(不是绝对一切,但足以开始!)


4
投票

指令获取是与代码获取的单独数据路径。它不是使用64位mov指令完成的。有专门的逻辑来处理读取和解码可变长度的未对齐x86指令。

单个指令可以跨越4k页面边界,因此它的字节来自2个不连续的物理页面!前端必须能够获取指令字节并将它们组装在缓冲区中。

甚至8086都有一个小的指令预取缓冲区,虽然这不一定是解码所必需的,因为在8088它比最长的指令(不包括前缀)小。


请参阅David Kanter's Sandybridge writeup,了解Sandybridge(以及Nehalem和Bulldozer)的前端图表。还有Agner Fog's microarch guide。有关AMD近期前端的更多信息,请参阅https://en.wikichip.org/wiki/amd/microarchitectures/zen#Decode

在P6和SnB系列Intel CPU上,代码获取和预解码(以查找insn边界)发生在16字节块中,每个周期最多可查找6个指令的长度,每个周期最多消耗16个字节的x86机器码。如果指令超过块的结尾,则预解码器将这些字节保持在下一个周期。 Agner Fog的microarch pdf有一些关于优化的细节,以避免预解码瓶颈; x86解码很难。例如在某些情况下,操作数大小前缀会更改指令其余部分的长度。例如66前缀是add eax, imm32(5字节)和add ax, imm1666 + 3字节)之间的唯一区别。英特尔CPU中的预编码器在这种情况下停止运行,需要额外的周期来处理它。 (亚历克西斯的回答称,长度调查很简单。多年来积累的所有ISA扩展肯定不容易,例如,VEX前缀是另一条指令的无效编码。而且它变得更难了当你试图并行执行多个指令时,因为在第一个指令之后你必须考虑所有指令的多个起始点。较旧的CPU曾经很慢地解码前缀,例如每个前缀或甚至转义字节需要额外的周期。但现代主流的英特尔(不是低功耗)可以处理任意数量的前缀而不会受到惩罚。)

指令一次最多4个(或5或6个宏融合)送入解码器。或者Skylake有5个解码器,如果有2对dec / jcc或其他宏可融合对,则最多可处理7条指令。根据uarch,这可以产生多达7个微操作(uops)(Core2 / Nehalem上的4-1-1-1模式),4(Skylake之前的SnB系列)或5(Skylake)。

enter image description here

并行解码x86指令是一个瓶颈,现代CPU(英特尔自SnB系列,AMD自Zen以来)将解码后的微处理缓存为快速部分代码。 Pentium 4的跟踪缓存是该方向的早期实验,其效果很差(并且它没有解码器吞吐量来维持跟踪缓存未命中的可接受性能)。

另请参阅What's the relationship between early 90s Pentium microprocessor and today's Intel designs?关于retrocomputing,我的答案谈到了为什么P4是CPU架构的死胡同,以及P6系列(PPro / PIII)如何演变成英特尔当前的Sandybridge系列。


所有x86-64 CPU都是新的,具有宽内部数据路径的高性能,但16位和32位CPU具有相同的15字节最大长度(包括冗余前缀)。他们可能会使用至少足够大的缓冲区来保存不包括前缀的指令,如果它们在查看操作码,modrm +额外寻址模式字节和/或立即数之前单独解码它们。

除了原始的8086之外,其中一个指令的64k代码段充满REP前缀是有效的。此时,英特尔没有对指令长度进行任何限制,并且8086解码前缀与指令的其余部分分开。

© www.soinside.com 2019 - 2024. All rights reserved.