赞
踩
目录
ARMv8架构中最显著的变化之一是引入了64位指令集。这个指令集补充了现有的32位指令集架构。这一增加使得处理器可以访问64位宽的整数寄存器和数据操作,并且可以使用64位大小的内存指针。这些新指令被称为A64,并在AArch64执行状态下执行。ARMv8还包括了原始的ARM指令集,现在称为A32,以及Thumb(T32)指令集。A32和T32都在AArch32状态下执行,并提供与ARMv7的向后兼容性。
尽管ARMv8-A提供了与32位ARM架构的向后兼容性,但A64指令集是独立且与旧的ISA不同的,并且编码方式也不同。A64增加了一些额外的功能,同时也移除了可能会限制高性能实现速度或能效的其他特性。ARMv8架构还包括对32位指令集(A32和T32)的一些增强。然而,使用这些特性的代码与旧的ARMv7实现不兼容。
A64指令集中的指令操作码(Instruction opcodes)仍然是32位长,而不是64位。寻求更详细描述A64汇编语言的程序员也可以参考ARM®编译器 armasm 参考指南 v6.01。
新的A64指令集与现有的A32指令集相似。指令宽度为32位,并且具有类似的语法。 ARMv8架构内使用通用的命名约定,因此原始的32位指令集状态现在被称为:
引入了一个新的指令集,当核心处于AArch64状态时可以使用。为了保持命名约定,并反映64位操作,这个指令集被称为:A64。
A64指令集在AArch32或ARMv7的A32和T32指令集中提供了类似的功能。新A64指令集的设计允许进行几项改进:
一致的编码方案:A32中一些指令的较晚添加导致了编码方案的不一致性。例如,LDR和STR对半字的支持与主流的字节和字传输指令的编码略有不同。这导致寻址模式略有不同。
广泛的常量范围:
复杂的常量规则:对于在位操作指令中使用的常量,有稍微更复杂的规则。然而,位字段操作指令可以寻址源操作数或目的操作数中的任何连续位序列。
数据类型更简单:A64指令集在处理64位有符号和无符号数据类型方面更为自然,因为它提供了更简洁、高效的方式来操作64位整数。这对所有提供64位整数的语言(如C或Java)都是有利的。
长偏移量: A64指令通常提供更长的偏移量,无论是对于PC相对分支还是对于偏移寻址。增加的分支范围使得管理跨段跳转更容易。动态生成的代码通常放置在堆上,因此在实践中可以位于任何位置。运行时系统更容易使用增加的分支范围来管理这种情况,并且需要的修正更少。
字面量池:(literal pools) ARM指令集长期以来一直有字面量池(嵌入在代码流中的字面量数据块)的特点。这在A64中仍然存在。然而,更大的PC相对加载偏移量在管理字面量池方面大有帮助,使得每个编译单元可以使用一个,这消除了在长代码序列中为多个池制造位置的需要。
指针: AArch64中的指针是64位的,这允许寻址更多的虚拟内存,并为地址映射提供了更多的自由度。然而,使用64位指针确实会带来一些成本。相同的代码在运行时使用64位指针通常比使用32位指针使用更多的内存。每个指针存储在内存中,需要8个字节而不是4个字节。这听起来可能微不足道,但可能会累积成显著的惩罚。此外,与转向64位相关的内存空间增加可能会减少缓存命中次数。缓存命中的减少可能会降低性能。一些语言可以通过使用压缩指针来规避性能问题,例如Java。
条件构造: 条件构造代替了IT块。IT块是T32的一个有用特性,它允许高效的序列,避免了围绕未执行指令的短前向分支的需要。然而,它们有时对硬件来说难以高效处理。A64移除了这些块,并用条件指令如CSEL(条件选择)或CINC(条件递增)等替换了它们。这些条件构造更直接,更容易处理,没有特殊情况。
更直观的位移和旋转行为: A32或T32的位移和旋转行为并不总是容易映射到高级语言预期的行为。 ARMv7提供了一个桶形移位器,可以作为数据处理指令的一部分使用。然而,指定移位类型和移位量需要一定数量的操作码位,这些位本可以用于其他地方。 因此,A64指令移除了很少使用的选项,并增加了新的显式指令来执行更复杂的位移操作。
代码生成:在生成代码时,无论是静态还是动态,对于常见的算术函数,A32和T32通常需要不同的指令或指令序列来处理不同的数据类型。而A64在这方面的操作更加一致,因此更容易为不同大小的数据类型生成通用的简单操作序列。
例如,在T32中,相同的指令可能会根据使用的寄存器(低寄存器或高寄存器)有不同的编码。A64指令集的编码则更加规则和合理化。因此,A64的汇编器通常需要的代码行数比T32的汇编器要少。
固定长度指令: 所有A64指令的长度都是相同的,这与T32不同,后者是一个变长指令集。这使得生成的代码序列的管理和跟踪更加容易,特别是对动态代码生成器有影响。
三个操作数映射得更好: A32通常保留了数据处理操作的真实三操作数结构。而T32则包含了大量的两操作数指令格式,这在生成代码时使其稍微不够灵活。A64坚持使用一致的三操作数语法,这进一步有助于指令集的规律性和同质性,对编译器也是有益的。
在A64指令集中,大多数整数指令有两种形式,分别在64位通用寄存器文件中操作32位或64位值。根据指令使用的寄存器名称,可以区分操作的位宽:
当选择32位指令形式时,以下事实成立:
即使32位指令形式的结果与等效64位指令形式计算的低32位结果无法区分,这种区分也适用。例如,可以使用64位ORR指令执行32位位或操作,然后简单地忽略结果的高32位。A64指令集包括ORR指令的单独32位和64位形式。
C和C++的LP64和LLP64数据模型预计将在AArch64上最常用。它们都将常用的int、short和char类型定义为32位或更少。通过在指令集中保持这种语义信息,实现可以利用这些信息。例如,避免消耗能量或周期来计算、转发和存储这些数据类型的未使用的高32位。实现可以自由利用这种自由以他们选择的任何方式来节省能量。
因此,新的A64指令集提供了不同的符号扩展和零扩展指令。此外,A64指令集意味着可以扩展并移位ADD、SUB、CMN或CMP指令的最终源寄存器,以及加载或存储指令的索引寄存器。这使得涉及64位数组指针和32位数组索引的数组索引计算得以高效实现。
在64位架构中,处理器可以在单个寄存器中存储64位值,这使得在程序中访问大量内存变得简单得多。在32位核心上执行的单个线程限于访问4GB的地址空间。该地址空间的大部分被操作系统内核、库代码、外设等预留使用。结果,空间不足意味着程序在执行时可能需要将一些数据映射到内存中或从内存中映射出去。拥有更大的地址空间和64位指针可以避免这个问题。它还使得内存映射文件等技术更具吸引力和方便使用。文件内容被映射到线程的内存映射中,即使物理RAM可能不足以包含整个文件。
其他改进的寻址特性包括:
独占访问:对字节、半字、字和双字的独占加载-存储。对一对双字的独占访问允许原子更新一对指针,例如循环列表插入。所有独占访问必须自然对齐,并且成对的双字访问必须对齐到数据大小的两倍,即对于一对64位值,对齐到128位。
增加的PC相对偏移寻址:PC相对字面量加载的偏移范围为±1MB。与A32的PC相对加载相比,这减少了字面量池的数量,并增加了函数之间字面量数据的共享。反过来,这减少了I-cache和TLB的污染。
大多数条件分支的范围为±1MB,预计足以满足大多数在单个函数内发生的条件分支。
无条件分支,包括分支和链接,范围为±128MB,预计足以跨越大多数可执行加载模块和共享对象的静态代码段,而无需链接器插入的veneers(垫片)。
Veneers(垫片):当链接器检测到分支目标超出范围时,会自动插入一小段代码。垫片成为原始分支的中间目标,然后垫片本身再分支到目标地址。
对齐检查:在AArch64中执行时,会对指令获取和使用堆栈指针的加载或存储执行额外的对齐检查,使PC或当前SP的错对齐检查成为可能。这种方法比强制PC或SP的正确对齐更可取,因为PC或SP的错对齐通常表示软件错误,例如软件中的地址损坏。
程序计数器对齐检查:每当尝试执行从AArch64中错对齐的PC获取的指令时,会生成与指令获取相关的异常。错对齐的PC定义为PC的最低两位不是00。PC错对齐在与目标异常级别相关联的异常综合症寄存器中被识别。
堆栈指针(SP)对齐检查:每当尝试使用堆栈指针作为基地址在AArch64中进行加载或存储时,会生成与数据内存访问相关的异常。错对齐的堆栈指针是基地址计算中使用的堆栈指针的最低四位不是0000。每当它被用作基地址时,堆栈指针必须16字节对齐。
SP对齐检查仅在AArch64中执行,并且可以为每个异常级别独立启用:
这些寻址改进提高了内存访问的灵活性和效率,同时增强了对齐检查,有助于避免软件错误和提高性能。
A64的64位寄存器库在大多数应用程序中有助于减少寄存器压力。A64过程调用标准(PCS)通过寄存器(X0-X7)传递多达八个参数。相比之下,A32和T32只在寄存器中传递四个参数,其余的通过栈来传递。
PCS还定义了一个专用的帧指针(FP),这使得调试和调用图分析更容易,因为它可以可靠地展开堆栈。有关更多信息,请参阅第9章《ARM 64位架构的ABI》。
采用64位宽整数寄存器的一个后果是编程语言使用的变量宽度不同。目前有几种标准模型在使用中,它们主要在定义整数、长整型和指针的大小上有所不同:
ILP32:在这种模型中,int、long和指针都是32位。这种模型与32位系统兼容,但不支持64位指针。
LP64:这是最常见的模型,其中int和指针是64位,而long是64位或32位(取决于具体实现),但通常与指针大小相同。这种模型支持大地址空间和大数据类型。
LLP64:在这种模型中,int是32位,而long和指针是64位。这种模型在某些需要32位整数以保持兼容性的系统上使用。
ILP64:所有基本数据类型,包括int、long和指针,都是64位。这种模型提供了最大的一致性和可移植性,但可能需要更多的内存。
LP32:这是一种较少使用的模型,其中int是32位,而指针是32位,但long是16位或32位,具体取决于实现。这种模型在某些嵌入式系统或旧系统中可能使用。
每种模型都有其特定的用途和优势,选择哪种模型取决于应用程序的需求、目标平台和兼容性考虑。例如,LP64模型由于其对64位指针的支持,通常用于需要大内存空间的服务器和高性能计算应用程序。而ILP32模型可能更适合那些需要与32位系统保持兼容性的应用程序。
在ARMv8架构中,单个应用程序不能在两种执行状态之间使用代码。与A32和T32指令集之间的互操作不同,ARMv8中没有在A64和A32或T32指令集之间的互操作性。为ARMv8处理器编写的A64代码不能在ARMv7 Cortex-A系列处理器上运行。然而,为ARMv7-A处理器编写的代码可以在ARMv8处理器的AArch32执行状态下运行。这在图5-1中进行了总结。
这意味着,虽然ARMv8提供了向后兼容的AArch32执行状态以运行旧的32位代码,但新的64位代码不能在旧的32位处理器上执行。这种设计选择确保了64位代码可以利用ARMv8处理器的全部功能,同时仍然允许旧的应用程序在新硬件上运行,尽管它们不能利用64位的优势。
开发者在编写应用程序时需要考虑目标平台的架构。如果应用程序需要运行在ARMv7和ARMv8处理器上,那么应该使用AArch32执行状态来编写代码。如果应用程序只需要在ARMv8上运行,并且希望利用64位的优势,那么可以使用A64指令集来编写代码。
这种区分也意味着操作系统和编译器需要支持在AArch32和AArch64执行状态之间切换,以确保应用程序可以正确地在不同版本的ARM处理器上运行。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。