15字节指令如何从存储器转移到CPU?

时间:2019-02-28 01:42:53

标签: assembly x86-64 cpu-architecture cpu-registers instruction-set

假设我们使用的是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位?

2 个答案:

答案 0 :(得分: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解码是 hard 。例如在某些情况下,操作数大小的前缀会更改指令的 rest 的长度。例如66前缀是add eax, imm32(5个字节)和add ax, imm1666 + 3个字节)之间的唯一区别。在这种情况下,英特尔CPU中的预解码器会停滞,需要花费额外的时间来处理。 (亚历克西斯的回答声称,找长很容易。对于多年来积累的所有ISA扩展,不是很容易,例如,VEX前缀是另一条指令的无效编码,例如当您尝试并行执行多条指令时,这会变得更加困难,因为您必须考虑在第一条指令之后的所有指令的多个起始点。以前的CPU解码前缀的速度较慢,例如需要额外的周期每个前缀甚至是转义字节。但是现代主流的Intel(非​​低功耗)可以处理任何数量的前缀而不会受到任何惩罚。)

指令一次最多馈给解码器4个(如果使用宏融合,则最多馈给5或6个)。或Skylake有5个解码器,如果有2对dec / jcc或其他宏可熔对,则可处理多达7条指令。根据不同的月份,最多可以产生7个微操作(微指令)(Core2 / Nehalem上为4-1-1-1模式),4个(Skylake之前的SnB系列)或5个(Skylake)。

enter image description here

并行解码x86指令是一个瓶颈,以至于现代CPU(Intel从SnB系列开始,Intel从Zen开始)缓存了已解码的指令,从而简化了热部分代码。奔腾4的跟踪缓存是朝该方向进行的早期实验,效果不佳(并且没有解码器吞吐量来保持跟踪缓存未命中的可接受性能)。

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


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

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

答案 1 :(得分:1)

指令编码

现代的X86指令是根据以下内容构建的:

  • 前缀(0、1、2、3、4)
  • VEX(0,2,3)
  • 操作码(1)
  • ModR / M(1)
  • SIB(0,1)
  • DISP(0、1、2、4)
  • IMM(0、1、2、4)

前缀为零至四个字节:

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

VEX

VEX用于AVX扩展(多数情况下)

OPCODE

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

ModR / M定义模式

它告诉我们此指令使用哪种寄存器和/或存储模式。有些说明并不支持所有可用模式。

规模,指数,基础

SIB是ModR / M的扩展。

位移

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

立即

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,它将在该FIFO字节中放入00h

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

您可以说这些FIFO是附加寄存器。寄存器文件与FIFO是相同的,只是它具有随机访问而不是机制的“ PUSH / POP”类型。两者都使用类似的技术(也称为内存)将数据保存在FIFO和寄存器中。

文档

我建议第一个文档,当前标题为:

  

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

从Intel那里很好地了解了可用的说明(不是绝对的所有内容,但是足够上手!)