第一章 前言
1.1 摘要
本文档描述了 Arm 架构的应用程序二进制接口 (ABI) 使用的过程调用标准。
1.2 关键词
过程调用、函数调用、调用约定、数据布局
1.3 如何找到本规范的最新版本或报告其中的缺陷
如果您的副本超过一年,请查看 Arm 开发人员网(https://developer.arm.com/architectures/system-architectures/software-standards/abi) 以获取更高版本。
请将本规范中的缺陷报告至 arm.eabi@arm.com。
1.4 许可证
略
1.5 非机密专有通知
略
1.6 内容
略
第二章 关于本文档
2.1 变更控制
略
2.2 参考
AAPCS
Procedure Call Standard for the Arm Architecture
AAELF
ELF for the Arm Architecture
BSABI
ABI for the Arm Architecture (Base Standard)
CPPABI
C++ ABI for the Arm Architecture
ARMARM
The Arm Architecture Reference Manual 2 nd edition, edited by David Seal, published by AddisonWessley
Arm Architecture Reference Manual Arm v7-A and Arm v7-R edition
ACLE
Arm C Language Extensions
GCPPABI
Generic C++ ABI
2.3 术语和缩写
本文档使用以下术语和缩写。
ABI 应用程序二进制接口:
1. 可执行文件必须符合的规范才能在特定的执行环境中执行。例如,用于 Arm 架构的 Linux ABI。
2. 独立生成的可重定位文件必须符合规范的一个特定方面,以便静态链接和可执行。例如,用于 Arm 架构的 C++ ABI、用于 Arm 架构的运行时 ABI、用于 Arm 架构的 C 库 ABI。
Arm-based基于 Arm 架构。
EABI适合嵌入式(有时称为独立式)应用程序需求的 ABI。
PCS过程调用标准。
AAPCSArm 架构的过程调用标准(本标准)。
APCSArm 过程调用标准(已过时)。
TPCSThumb 过程调用标准(已过时)。
ATPCSArm-Thumb 过程调用标准(本标准的前身)。
PIC / PID与位置无关的代码,与位置无关的数据。
Routine / subroutine可以将控制权转移到的程序片段,在完成其任务后,在调用之后的指令处将控制权返回给其调用者。例程用于明确存在嵌套调用的位置:例程是调用者,子例程是被调用者。
Procedure不返回结果值的例程。
Function返回结果值的例程。
Activation stack / call-frame stack例程激活记录(调用帧)堆栈。
Activation record / call frame例程用于保存寄存器和保存局部变量的内存(通常分配在堆栈上,每次激活例程一次)。
Argument / Parameter术语参数和概念:参数可以互换使用。根据上下文,它们可以表示在调用例程时给定实际参数的值的例程的形式参数,或者表示实际参数。
Externally visible [interface][一个接口]分别编译或单独组装的例程之间。
Variadic routine如果例程采用的参数数量及其类型由调用者而不是被调用者确定,则该例程是可变参数的。
Global register一个寄存器,其值既不会被子程序保存也不会被子程序破坏。该值可以更新,但只能以执行环境定义的方式进行。
Program state程序内存的状态,包括机器寄存器中的值。
Scratch register / temporary register用于在计算期间保存中间值的寄存器(通常,这些值在程序源中没有命名,并且具有有限的生命周期)。
Thumb-1在 Arm v4T 中引入并在 Arm v6-M 和 Arm v8-M.Baseline 架构的变体中使用的 Thumb 指令集变体。它由主要使用 16 位操作码编码的指令组成。
Thumb-2Arm v6T2 中引入的 Thumb 指令集的变体。它由使用 16 位和 32 位操作码编码的混合指令组成。
Variable register / v-register用于保存变量值的寄存器,通常是例程的本地变量,通常在源代码中命名。
第三章范围
AAPCS 定义了如何单独编写、单独编译和单独组装子例程以协同工作。它描述了调用例程和被调用例程之间的契约,该契约定义:
• 调用者有义务创建被调用例程可以开始执行的程序状态。
• 被调用例程有义务在整个调用过程中保持调用者的程序状态。
• 被调用例程改变其调用者程序状态的权利。
本标准规定了一系列过程调用标准 (PCS) 变体的基础,这些变体由反映以下备选优先级的选择生成:
• 代码大小。
• 表现。
• 功能(例如,易于调试、运行时检查、对共享库的支持)。
每个变体的某些方面——例如 R9 的允许使用——由执行环境决定。因此:
• 严格遵守基本标准的代码可能与每个变体的 PCS 兼容。
• 符合变体的代码与符合任何其他变体的代码兼容是不寻常的。
• 不保证符合变体或基本标准的代码与需要这些标准的执行环境兼容。执行环境可能会提出超出过程调用标准范围的进一步要求。
该标准分为四个部分,在介绍之后,指定:
• 数据布局。
• 堆栈的布局以及在具有公共接口的函数之间的调用。
• 可用于处理器扩展的变体,或者当执行环境限制广告修饰模型时。
• 普通数据类型的C 和C++ 语言绑定。
本规范没有标准化公开可见的 C++ 语言实体的表示,这些实体也不是 C 语言实体(这些在 CPPABI 中进行了描述),并且它对跨公共接口不可见的语言实体的表示没有任何要求。
第四章介绍
AAPCS 体现了 APCS 的第五次主要修订和 TPCS 的第三次主要修订。它构成了 Arm 架构的完整 ABI 规范的一部分。
4.1 设计目标
AAPCS 的目标是:
• 平等地支持Thumb状态和Arm状态。
• 支持Thumb-state 和Arm-state 之间的互通。
• 支持高效执行 Arm 架构的高性能实现。
• 明确区分强制性要求和实施自由裁量权。
• 尽量减少与ATPCS 的二进制不兼容性。
4.2 一致性
AAPCS 定义了单独编译和单独组装的例程如何一起工作。这些例程之间有一个外部可见的接口。通常,并非所有外部可见的软件接口都旨在公开可见或对任意使用开放。实际上,外部可见性的机器级概念(由目标代码格式严格定义)与更高级别的、面向应用的外部可见性概念(特定于系统或特定于应用程序)之间存在不匹配。
符合 AAPCS 要求(注11):
• 始终遵守堆栈限制和基本堆栈对齐(通用堆栈约束(第 21 页))。
• 在控制转移指令在静态链接时受到BL 类型重定位的每个调用中,遵守IP 使用规则(链接器使用IP(第23 页))。
• 每个公开可见接口的例程都符合相关的过程调用标准变体。
• 每个公开可见界面的数据元素(注12)符合数据布局规则。
注11:这种一致性定义为实现者提供了最大的自由。例如,如果已知外部可见接口的两端将由同一个编译器编译,并且该接口不会公开可见,则 AAPCS 允许跨接口使用私有安排,例如使用额外的参数寄存器或以非标准格式传递数据。然而,堆栈不变量必须保留,因为调用链中其他地方的符合 AAPCS 的例程可能会失败。必须遵守 IP 使用规则,否则静态链接器可能会生成无法运行的可执行程序。公开可见界面的一致性不取决于该界面背后发生的事情。因此,例如,一棵非公开的、不符合要求的调用树可以符合要求,因为树的根提供了一个公开可见的符合要求的接口,并且满足了其他约束。
注12:数据元素包括:接口中命名的例程的参数、接口中命名的静态数据以及通过接口传递的指针值寻址的所有数据。
第五章数据类型和对齐
5.1 基本数据类型
表 1,基本数据类型的字节大小和字节对齐(第 14 页)显示了机器的基本数据类型(机器类型)。 NULL 指针始终由所有位为零表示。
表 5.1:表 1,基本数据类型的字节大小和字节对齐
5.1.1 半精度浮点
Arm 架构的可选扩展为半精度值提供硬件支持。当前支持三种格式:
1 – IEEE754-2008 中指定的半精度格式
2 – Arm Alternative 格式,提供额外的范围但没有 NaN 或无穷大。
3 – Brain 浮点格式,提供类似于 32 位浮点格式的动态范围,但精度较低。
前两种格式是互斥的。 AAPCS 的基本标准指定使用 IEEE754-2008 变体,并且允许使用 Arm Alternative 格式的过程调用变体。
5.1.2 容器化向量容
器化向量的内容对于大多数过程调用标准是不透明的:其布局的唯一定义方面是内存格式(基本类型存储在内存中的方式)和不同类之间的映射。在过程调用接口注册。如果语言绑定定义了直接映射到容器化向量的数据类型,它将定义如何执行此映射。
5.2 字节序和字节顺序
从软件的角度来看,内存是一个字节数组,每个字节都是可寻址的。
此 ABI 支持由底层硬件实现的两种内存视图。
• 在内存的little-endian 视图中,数据对象的最低有效字节位于数据对象在内存中占用的最低字节地址。
• 在内存的大端视图中,数据对象的最低有效字节位于数据对象在内存中占用的最高字节地址。
对象中的最低有效位始终指定为位 0。
字大小的数据对象到内存的映射显示在大端数据对象的内存布局(第 15 页)和小端数据对象的内存布局(第 16 页)中。所有对象都是纯字节序的,因此映射可以针对更大或更小的对象进行相应缩放(注13)。
注13:底层硬件可能不直接支持非自然对齐的数据对象的纯端视图。
图 5.1:大端数据对象的内存布局
5.3 复合类型
复合类型是一个或多个基本数据类型的集合,在过程调用级别作为单个实体处理。复合类型可以是以下任何一种:
• 聚合,其中成员在内存中按顺序排列
• 联合,其中每个成员具有相同的地址
• 数组,它是某种其他类型的重复序列(其基类型)。
定义是递归的;也就是说,每个类型都可以包含一个复合类型作为成员。
图 5.2:little-endian 数据对象的内存布局
• 复合类型元素的成员对齐是在对该成员应用任何语言对齐修饰符后该成员的对齐
• 复合类型的自然对齐是复合类型的“顶级”成员的每个成员对齐的最大值,即在应用整个复合的任何对齐调整之前
5.3.1 聚合
• 聚合的对齐应为其最对齐的组件的对齐。
• 聚合的大小应为其对齐的最小倍数,当它们根据这些规则布置时足以容纳其所有成员。
5.3.2 联合
• 联合的对齐应为其最对齐的组件的对齐。
• 联合的大小应该是足以容纳其最大成员的对齐的最小倍数。
5.3.3 数组
• 数组的对齐方式应为其基本类型的对齐方式。
• 数组的大小应为基本类型的大小乘以数组中的元素数。
5.3.4 位域
作为基本数据类型的聚合的成员可以细分为位域;如果此类成员的未使用部分足以以自然对齐方式启动后续成员,则后续成员可以使用未分配部分。为了计算聚合的对齐方式,成员的类型应为位域所基于的基本数据类型(注14)。聚合内的位域布局由适当的语言绑定定义。
注14:目的是允许 C 构造 struct {int a:8; char b[7];} 的大小为 8,对齐方式为 4。
5.3.5 同构聚合
同构聚合是一种复合类型,其中构成该类型的所有基本数据类型都相同。同质性测试在数据布局完成后应用,不考虑访问控制或其他源语言限制。
如果所有成员的大小相同,则由容器化向量类型组成的聚合被视为同质的,即使容器化成员的内部格式不同。例如,包含 8 个字节的向量和 4 个半字的向量的结构满足同构聚合的要求。
同构聚合有一个基本类型,它是每个元素的基本数据类型。整体大小是 Base Type 的大小乘以 Element 的数量;它的对齐方式将是 Base Type 的对齐方式。
第六章基本程序调用标准
基本标准定义了 Arm 和 Thumb 指令集通用的机器级、仅核心寄存器调用标准。它应该用于没有浮点硬件的系统,或者需要与 Thumb 代码高度交互的系统。
6.1 机器寄存器
Arm 架构定义了一个核心指令集以及由协处理器实现的许多附加指令。核心指令集可以访问核心寄存器,协处理器可以提供可用于特定操作的附加寄存器。
6.1.1 核心寄存器
Arm 和 Thumb 指令集可以看到 16 个 32 位核心(整数)寄存器。这些标记为 r0-r15 或 R0-R15。寄存器名称可以以大写或小写形式出现在汇编语言中。在本规范中,当寄存器在过程调用标准中具有固定作用时,使用大写字母。表 2,核心寄存器和 AAPCS 使用(第 18 页)总结了本标准中核心寄存器的使用。除了核心寄存器之外,还有一个状态寄存器 (CPSR) 可用于一致性代码。
表 6.1:表 2,核心寄存器和 AAPCS 使用
前四个寄存器 r0-r3 (a1-a4) 用于将参数值传递给子程序并从函数返回结果值。它们也可用于在例程中保存中间值(但通常仅在子例程调用之间)。
寄存器 r12 (IP) 可以被链接器用作例程和它调用的任何子例程之间的临时寄存器(有关详细信息,请参阅链接器使用 IP(第 23 页))。它也可以在例程中用于保存子例程调用之间的中间值。
在某些变体中,r11 (FP) 可以用作帧指针,以便将帧激活记录链接到链表中。
寄存器 r9 的作用是特定于平台的。虚拟平台可以为该寄存器分配任何角色,并且必须记录此用法。例如,它可以在与位置无关的数据模型中将其指定为静态基址(SB),或者在具有线程本地存储的环境中将其指定为线程寄存器(TR)。该寄存器的使用可能要求所保存的值在所有调用中都是持久的。不需要这种特殊寄存器的虚拟平台可以将 r9 指定为附加的被调用者保存的变量寄存器 v6。
通常,寄存器 r4-r8、r10 和 r11(v1-v5、v7 和 v8)用于保存例程的局部变量的值。其中,只有 v1-v4 可以被整个 Thumb 指令集统一使用,但 AAPCS 并不要求 Thumb 代码只使用这些寄存器。
子程序必须保留寄存器 r4-r8、r10、r11 和 SP(以及将 r9 指定为 v6 的 PCS 变体中的 r9)的内容。
在过程调用标准的所有变体中,寄存器 r12-r15 具有特殊的作用。在这些角色中,它们被标记为 IP、SP、LR 和 PC。
CPSR 是具有以下属性的全局寄存器:
• N、Z、C、V 和 Q 位(位 27-31)和 GE[3:0] 位(位 16-19)在进入或从公共接口返回。 Q 和 GE[3:0] 位只能在具有这些特性的处理器上执行时被修改。
• 在 Arm 架构 6 上,E 位(位 8)可用于以 little-endian 模式或 big-endian-8 模式执行的应用程序,以临时更改对内存的数据访问的字节序。
应用程序必须具有指定的字节序,并且在进入和从任何公共接口返回时,E 位的设置必须与应用程序的指定字节序匹配。
• T 位(第 5 位)和 J 位(第 24 位)是执行状态位。只有指定用于修改这些位的指令才能更改它们。
• A、I、F 和 M[4:0] 位(位 0-7)是特权位,只能由设计为在特权模式下显式操作的应用程序修改。
• 所有其他位都保留,不得修改。未定义位读取为 0 还是 1,或者它们是否通过公共接口保留。
处理大于 32 位的值
大于 32 位的基本类型可以作为参数传递给函数调用,或作为函数调用的结果返回。当这些类型在核心寄存器中时,以下规则适用:
• 双字大小的类型在两个连续的寄存器中传递(例如,r0 和 r1,或 r2 和 r3)。
寄存器的内容就像是用一条 LDM 指令从内存表示中加载了值一样。
• 128 位容器化向量在四个连续寄存器中传递。寄存器的内容就像是用一条 LDM 指令从内存中加载了值一样。
6.1.2 协处理器寄存器
机器的寄存器集可以扩展为通过协处理器指令空间中的指令访问的附加寄存器。就这些寄存器不用于向子程序调用和从子程序调用传递参数而言,协处理器寄存器的使用与基本标准兼容。每个协处理器可以提供一组额外的规则来管理其寄存器的使用。
注意:即使协处理器寄存器不用于传递参数,语言的运行时支持的某些元素可能需要了解应用程序中使用的所有协处理器才能正常运行(例如,setjmp() C 和 C++ 中的异常)。
VFP 寄存器使用约定
VFP-v2 协处理器有 32 个单精度寄存器 s0-s31,也可以作为 16 个双精度寄存器 d0-d15 访问(其中 d0 与 s0、s1 重叠;d1 与 s2、s3 重叠等)。此外,还有 3 个或更多系统寄存器,具体取决于实现。 VFP-v3 增加了 16 个双精度寄存器 d16-d31,但没有额外的单精度寄存器。高级 SIMD 扩展和 M-profile 矢量扩展 (MVE) 使用 VFP 寄存器集。高级 SIMD 扩展将双精度寄存器用于 64 位向量,并进一步定义了 128 位向量的四字寄存器(q0 与 d0、d1 重叠;q1 与 d2、d3 等重叠)。 MVE 在相同的四字寄存器中使用 128 位向量。
寄存器 s16-s31 (d8-d15, q4-q7) 必须在子程序调用中保留;寄存器 s0-s15 (d0-d7, q0-q3) 不需要保留(并且可用于在标准过程调用变体中传递参数或返回结果)。如果存在寄存器 d16-d31 (q8-q15),则不需要保留。
FPSCR 和 VPR 寄存器是唯一可以通过一致性代码访问的状态寄存器。
FPSCR 是具有以下属性的全局寄存器:
• 条件代码位 (28-31)、累积饱和 (QC) 位 (27) 和累积异常状态位 (0-4 和 7)公共接口。
• 异常控制位(8-12 和15)、舍入模式位(22-23)和清零位(24)可以通过调用影响应用程序全局状态的特定支持函数来修改。
• 长度位(16-18)在使用M-profile 矢量扩展时必须为0b100,在使用VFP 矢量模式时必须为0b000,否则在公共接口上保留。
• 在进入公共接口和从公共接口返回时,步幅位 (20-21) 必须为零。
• 所有其他位都保留,不得修改。未定义位读取为 0 还是 1,或者它们是否通过公共接口保留。
VPR 是具有以下属性的全局寄存器:
• VPT 掩码位 (16-23) 在进入和返回公共接口时必须为零。
• 预测位(0-15) 不跨公共接口保留。
• 所有其他位都保留,不得修改。未定义位读取为 0 还是 1,或者它们是否通过公共接口保留。
6.2 进程、内存和堆栈
AAPCS 适用于单个执行线程或进程(以下简称进程)。进程具有由底层机器寄存器和它可以访问的内存内容定义的程序状态。进程可以访问的内存不会导致运行时错误,可能会在进程执行期间发生变化。
进程的内存通常可以分为五类:
• 代码(正在执行的程序),必须是进程可读的,但不必是可写的。
• 只读静态数据。
• 可写静态数据。
• 堆。
• 堆栈。
可写静态数据可以进一步细分为已初始化、零初始化和未初始化数据。
除了堆栈之外,不需要每类内存占用一个连续的内存区域。一个进程必须总是有一些代码和一个堆栈,但不需要有任何其他类别的内存。
堆是由进程本身管理的一个(或多个)内存区域(例如,使用 C malloc 函数)。它通常用于创建动态数据对象。
符合标准的程序必须只执行位于指定包含代码的内存区域中的指令。
6.2.1 堆栈
堆栈是一个连续的内存区域,可用于存储局部变量以及在没有足够的参数寄存器可用时将附加参数传递给子程序。
堆栈实现是全降序的,堆栈的当前范围保存在寄存器 SP (r13) 中。通常,堆栈将同时具有基础和限制,尽管在实践中应用程序可能无法确定其中任何一个的值。
堆栈可能具有固定大小或可动态扩展(通过向下调整堆栈限制)。
堆栈维护的规则分为两部分:必须始终遵守的一组约束,以及必须在公共接口处遵守的附加约束。
通用堆栈约束
在任何时候都必须满足以下基本约束:
• Stack-limit < SP <= stack-base。堆栈指针必须位于堆栈范围内。
• SP mod 4 = 0。堆栈必须始终与字边界对齐。
• 进程只能将数据存储在由[SP, stack base – 1] 分隔的整个堆栈的封闭区间中(其中SP 是寄存器r13 的值)。
注意:这意味着以下形式的指令可能无法满足堆栈规则约束,即使 reg 指向堆栈范围内。
如果在加载 sp 之后指令的执行被中断,堆栈范围将不会被恢复,因此重新启动指令可能会违反第三个约束。
公共接口的堆栈约束
堆栈还必须符合公共接口的以下约束:
• SP mod 8 = 0。堆栈必须是双字对齐的
堆栈探测
为了确保堆栈完整性,进程可能会在分配额外堆栈空间之前立即发出堆栈探测(将 SP 从 SP_old 移动到 SP_new)。堆栈探测必须在 [SP_new, SP_old – 1] 的区域内,并且可以是读取或写入操作。堆栈探测的最小间隔由目标平台定义,但必须至少为 4KBytes。当前分配的堆栈区域下方不能保存可恢复的数据。
帧指针
一个平台可能需要构建一个堆栈帧列表来描述程序中的当前调用层次结构。
每个帧都应通过堆栈上两个 32 位值的帧记录链接到其调用者的帧。最内层帧的帧记录(属于最近的例程调用)应由帧指针寄存器(FP)指向。最低地址字应指向前一帧记录,最高地址字应包含在进入当前函数时传入 LR 的值。帧记录链的末端由前一帧地址中的地址零表示。未指定堆栈帧中帧记录的位置。在完全构建新的帧记录之前,不得更新帧指针寄存器。
注意:在每个帧记录的构造或销毁过程中,总会有一个短暂的时间段,在此期间,帧指针将指向调用者的记录。
平台应规定维护框架记录的最低一致性水平。选项在功能级别降低时:
• 可能需要帧指针始终寻址有效的帧记录,除了不修改链接寄存器的小型子程序可能选择不创建帧记录
• 它可能需要帧指针始终寻址一个有效的帧记录,除了任何子程序可以选择不创建帧记录
• 它可以允许帧指针寄存器用作通用的被调用者保存寄存器,但提供一个平台- 外部代理可靠定位帧记录链的特定机制
• 它可以选择不维护帧链并使用帧指针寄存器作为通用的被调用者保存寄存器。
注意:与 APCS 及其变体不同,相同的帧指针寄存器用于 Arm 和 Thumb ISA(包括 Thumb-1 变体),这确保即使在生成的代码在两者之间互通时也可以构建帧链Arm 和 Thumb 指令集。预计 Thumb-1 代码很少(如果有的话)想要创建堆栈帧 – 因此选择高位寄存器可确保此类代码可以最低限度地符合将有效值存储在帧指针寄存器中的要求,而不会显着地减少可用于普通代码的寄存器数量。
AAPCS 没有指定在函数的堆栈帧记录中,帧链数据结构所在的位置。这允许实现者自由使用任何位置将导致建立帧链记录所需的最有效代码。因此,即使在 Thumb-1 中,建立帧的开销也很少会超过函数入口序列中的三个附加指令和返回序列中的两个附加指令。
6.3 子程序调用
Arm 和 Thumb 指令集都包含一个原始子程序调用指令 BL,它执行带有链接的分支操作。执行 BL 的效果是将程序计数器的下一个值(返回地址)依次传送到链接寄存器 (LR) 中,并将目标地址传送到程序计数器 (PC) 中。如果 BL 指令从 Thumb 状态执行,链接寄存器的位 0 将设置为 1,如果从 Arm 状态执行,则设置为 0。结果是将控制转移到目标地址,将 LR 中的返回地址作为附加参数传递给被调用的子程序。
当返回地址被加载回 PC 时,控制权返回到 BL 之后的指令(参见互通(第 26 页))。
子程序调用可以由具有以下效果的任何指令序列合成:
例如,在 Arm 状态下,要调用由 r4 寻址的子程序,控制返回到以下指令,请执行
注意:等效序列在 Thumb 状态下不起作用,因为设置 LR 的指令不会将 Thumb 状态位复制到 LR[0]。
在 Arm 架构 v5 中,Arm 和 Thumb 状态都提供 BLX 指令,该指令将调用由寄存器寻址的子例程,并将返回地址正确设置为程序计数器的顺序下一个值。
6.3.1 链接器对 IP 的使用
Arm 和 Thumb 状态的 BL 指令都无法寻址完整的 32 位地址空间,因此链接器可能需要在调用例程和被调用例程之间插入一个胶合代码子程序。可能还需要单板来支持 Arm-Thumb 互通或动态链接。插入的任何单板都必须保留除 IP (r12) 和条件代码标志之外的所有寄存器的内容;一致的程序必须假设可以在任何暴露于支持互通或长分支的重定位的分支指令中插入改变 IP 的胶合代码。
注意:R_ARM_CALL、R_ARM_JUMP24、R_ARM_PC24、R_ARM_THM_CALL、R_ARM_THM_JUMP24 和 R_ARM_THM_JUMP19 是具有此属性的 ELF 重定位类型的示例。详情请参阅 [AAELF]。
6.4 结果返回
函数返回结果的方式由结果的类型决定。
对于基本标准:
• 在 r0 的最低有效 16 位中返回半精度浮点类型。
• 小于 4 个字节的基本数据类型以零或符号扩展为一个字并在 r0 中返回。
• 在 r0 中返回一个字长的基本数据类型(例如,int、float)。
• 在 r0 和 r1 中返回双字大小的基本数据类型(例如,long long、double 和 64 位容器化向量)。
• 在r0-r3 中返回一个128 位容器化向量。
• 在 r0 中返回不大于 4 个字节的复合类型。这种格式就好像结果已经存储在内存中的字对齐地址,然后使用 LDR 指令加载到 r0 中。 r0 中位于结果范围之外的任何位都具有未指定的值。
• 大于 4 字节的复合类型,或者其大小不能由调用者和被调用者静态确定,存储在内存中的地址处,该地址在调用函数时作为额外参数传递(参数传递(第 24 页),规则 A. 4(第 25 页))。用于结果的内存可以在函数调用期间的任何时候修改。
6.5 参数传递
基本标准规定在核心寄存器(r0-r3)和堆栈中传递参数。对于带少量参数的子程序,只使用寄存器,大大减少了调用的开销。
参数传递被定义为一个两级概念模型
• 从源语言参数到机器类型的映射
• 机器类型的编组以产生最终参数列表
从源语言到机器类型的映射对于每种语言都是特定的并单独描述(C 和 C++ 语言绑定在 Arm C 和 C++ 语言映射 pings(第 31 页)中描述)。结果是要传递给子例程的参数的有序列表。
在下面的描述中,假设有许多协处理器可用于传递和接收参数。协处理器寄存器分为不同的类别。一个参数最多可以是一个协处理器寄存器类的候选者。适合分配给协处理器寄存器的参数称为协处理器候选寄存器 (CPRC)。
在基本标准中,没有参数可以作为协处理器寄存器类的候选者。
可变参数函数始终按照基本标准进行编组。
对于调用者,假设在编组之前已经分配了足够的堆栈空间来保存堆栈参数:实际上,直到参数编组完成后才能知道所需的堆栈空间量。被调用者可以修改用于从调用者接收参数值的任何堆栈空间。
当复合类型参数分配给核心寄存器(全部或部分)时,行为就像参数已存储在字对齐(4 字节)地址的内存中,然后使用合适的加载多指令加载到连续的寄存器中。
阶段 A – 初始化
在开始处理参数之前,此阶段只执行一次。
A.1 下一个核心寄存器编号 (NCRN) 设置为 r0。
A.2.cp执行协处理器参数寄存器初始化。
A.3 下一个堆栈参数地址 (NSAA) 设置为当前堆栈指针值 (SP)。
A.4 如果子程序是一个在内存中返回结果的函数,则结果的地址放在 r0 中,NCRN 设置为 r1。
阶段 B – 参数的预填充和扩展
对于列表中的每个参数,将应用以下列表中的第一个匹配规则。
B.1 如果参数是复合类型,其大小不能由调用者和被调用者静态确定,则将参数复制到内存中,并将参数替换为指向副本的指针。
B.2 如果参数是小于一个字的整数基本数据类型,则它被零或符号扩展为一个完整的字,其大小设置为 4 个字节。如果参数是半精度浮点类型,则其大小设置为 4 字节,就好像它已被复制到 32 位寄存器的最低有效位,其余位填充未指定的值。
B.3.cp 如果参数是 CPRC,则应用该协处理器寄存器类的任何准备规则。
B.4 如果参数是大小不是 4 字节的倍数的复合类型,则其大小向上舍入到最接近的 4 倍数。
B.5 如果参数是对齐调整类型,则其值作为实际值的副本传递。该副本将具有如下定义的对齐方式。
• 对于基本数据类型,对齐是该类型在任何提升之后的自然对齐。
• 对于复合类型,如果副本的自然对齐 = 8,则副本的对齐将具有8 字节对齐。
副本的对齐用于应用封送规则。
阶段 C – 将参数分配给寄存器和堆栈
对于列表中的每个参数,依次应用以下规则,直到参数被分配。
C.1.cp 如果参数是 CPRC 并且有足够的适当类的未分配协处理器寄存器,则将参数分配给协处理器寄存器。
C.2.cp 如果参数是 CPRC,则该类中未分配的任何协处理器寄存器都被标记为不可用。 NSAA 向上调整,直到它与参数正确对齐,并且参数被复制到调整后的 NSAA 的内存中。 NSAA 进一步增加了参数的大小。现在已经分配了参数。
C.3 如果参数需要双字对齐(8 字节),则 NCRN 向上舍入到下一个偶数寄存器号。
C.4 如果参数的字大小不大于 r4 减去 NCRN,则将参数复制到核心寄存器中,从 NCRN 开始。 NCRN 按所使用的寄存器数递增。如果使用 LDM 指令将其值从内存加载到这些寄存器中,则连续的寄存器保存它们将保存的参数部分。现在已经分配了参数。
C.5 如果 NCRN 小于 r4 并且 NSAA 等于 SP,则参数在核心寄存器和堆栈之间拆分。参数的第一部分被复制到从 NCRN 开始直到 r3 并包括在内的核心寄存器中。参数的其余部分被复制到堆栈中,从 NSAA 开始。 NCRN 设置为 r4,NSAA 的增量为参数大小减去寄存器中传递的数量。现在已经分配了参数。
C.6 NCRN 设置为 r4。
C.7 如果参数需要双字对齐(8 字节),则 NSAA 向上舍入到下一个双字地址。
C.8 论证被复制到 NSAA 的内存中。 NSAA 按参数的大小递增。
应该注意的是,上述算法为 C 和 C++ 以外的语言提供了条件,因为它提供了按值传递数组和传递动态大小的参数。规则的定义方式允许调用者始终能够静态确定必须为未在寄存器中传递的参数分配的堆栈空间量,即使函数是可变参数的。
还可以进一步观察:
• 初始堆栈槽地址是将传递给子程序的堆栈指针的值。
因此,在编译期间可能需要运行上述算法两次,一次确定参数所需的堆栈空间量,第二次分配最终的堆栈槽地址。
• 双字对齐类型将始终从偶数核心寄存器开始,或者从堆栈上的双字对齐地址开始,即使它不是聚合的第一个成员。
• 参数首先分配给寄存器,只有多余的参数放在堆栈上。
• 作为基本数据类型的参数可以完全在寄存器中或完全在堆栈中。
• 根据规则 C.5(第 25 页),最多可以在寄存器和内存之间拆分一个参数。
• CPRC 可以分配给协处理器寄存器或堆栈——它们可能永远不会分配给核心寄存器。
• 由于一个参数最多可能是一类协处理器寄存器的候选者,因此多个协处理器的规则(如果它们存在)可以以任何顺序应用而不影响行为。
• 如果所有前面的CPRC 都已分配给协处理器寄存器,则只能在核心寄存器和堆栈之间拆分参数。
6.6 互通
AAPCS 要求所有子程序调用和返回序列支持 Arm 和 Thumb 状态之间的互通。编译各种 Arm 架构的含义如下。
Arm v5 和 Arm v6
通过函数指针的调用应酌情使用以下之一:
如果需要更改状态,调用使用 bl、b 或 b 的函数将需要链接器生成的胶合代码,因此有时使用允许使用无条件 bl 指令的序列可能更有效.
返回序列可以使用直接加载 PC 的加载多重操作或合适的 bx 指令。
如果可能需要互通,则不得使用以下传统返回。
Arm v4T
除了 Arm v5 的限制之外,以下附加限制适用于 Arm v4T。
使用 bl 的涉及状态更改的调用也需要链接器生成的存根。
通过函数指针调用必须使用与 Arm 状态代码等效的序列
但是,此序列不适用于 Thumb 状态,因此通常必须使用 bl 到执行 bx 指令的胶合板。
返回序列必须恢复所有保存的寄存器,然后使用 bx 指令返回给调用者。
Arm v4
Arm v4 架构既不支持 Thumb 状态也不支持 bx 指令,因此它与 AAPCS 不严格兼容。
建议使用 Arm v4T 互通序列编译 Arm v4 的代码,但所有 bx 指令都受 R_ARM_V4BX 重定位 [AAELF] 的重定位。然后,用于 Arm V4 的链接器可以更改以下所有实例:
Into:
但是可重定位文件仍然与此标准兼容
第七章 标准变体
本节仅适用于非可变函数。对于可变参数函数,基本标准始终用于参数传递和结果返回。
7.1 VFP 和 SIMD 向量寄存器参数
这个变体改变了浮点值在子例程和它的调用者之间传递的方式,并且在存在 VFP 协处理器、高级 SIMD 扩展或 M-profile 矢量扩展时允许显着更好的性能。
7.1.1 寄存器与内存格式的映射
通过 VFP 寄存器中的过程调用接口传递的值的布局如下:
• 传递半精度浮点类型,就好像它从其内存格式加载到单精度寄存器的最低有效 16 位中一样。
• 传递单精度浮点类型,就好像它是从其内存格式加载到带有VLDR 的单精度寄存器中一样。
• 传递双精度浮点类型,就好像它是从其内存格式加载到带有VLDR 的双精度寄存器中一样。
• 传递 64 位容器化向量类型,就好像它已从其内存格式加载到具有 VLDR 的 64 位向量寄存器 (Dn) 中一样。
• 传递一个 128 位容器化向量类型,就好像它从其内存格式加载到一个 128 位向量寄存器 (Qn) 中一样,其中包含两个组件 64 位向量寄存器中的一个 VLDM(例如,VLDM r0,{ d2,d3} 将加载 q1)。
7.1.2 过程调用
调用保存的寄存器集与基本标准相同(VFP 寄存器使用约定(第 20 页))。
VFP 协处理器寄存器候选
对于 VFP,以下参数类型是 VFP CPRC。
• 半精度浮点类型。
• 单精度浮点类型。
• 双精度浮点类型。
• 64 位或 128 位容器化向量类型。
• 具有单精度或双精度浮点类型的基类型和一到四个元素的同构聚合。
• 具有基本类型为64 位容器化向量的同构聚合,其中包含一到四个元素。
• 具有128 位容器化向量基本类型的同构聚合,其中包含一到四个元素。
注意:可变参数程序中没有 VFP CPRC。
结果返回
其类型满足 VFP CPRC 条件的任何结果都将在适当数量的连续 VFP 寄存器中返回,从最低编号的寄存器 (s0, d0, q0) 开始。
所有其他类型都按照基本标准返回。
参数传递
有一个 VFP 协处理器寄存器类使用寄存器 s0-s15 (d0-d7) 来传递参数。
为 VFP 定义了以下协处理器规则:
A.2.vfp 浮点参数寄存器被标记为未分配。
B.3.vfp 无事可做。
C.1.vfp 如果参数是一个 VFP CPRC 并且有足够多的适当类型的连续 VFP 寄存器未分配,则该参数被分配给此类寄存器的最低编号序列。
C.2.vfp 如果参数是 VFP CPRC,那么任何未分配的 VFP 寄存器都被标记为不可用。 NSAA 向上调整,直到它与参数正确对齐,并且参数被复制到调整后的 NSAA 的堆栈中。 NSAA 进一步增加了参数的大小。现在已经分配了参数。
请注意,规则要求“回填”未使用的协处理器寄存器,这些寄存器被早期参数的对齐约束跳过。仅当没有 VFP CPRC 分配给堆栈上的插槽时,回填才会继续。
7.2 Arm 替代格式半精度浮点值
可以编译代码以使用 Arm 替代格式半精度值。传递和返回值的规则将使用基本标准规则或 VFP 和 SIMD 向量寄存器规则。
7.3 读写位置独立性 (RWPI)
为需要读写位置独立性的执行环境(例如,单地址空间 DLL 类模型)编译或组装的代码使用静态基址来寻址可写数据。核心寄存器 r9 被重命名为 SB 并用于保存静态基地址:因此该寄存器在任何时候都不能用于保存其他值(注15)
注15:尽管本标准没有强制要求,但编译器通常通过从 SB 加载数据的偏移量并将 SB 添加到它来制定静态数据的地址。通常,偏移量是从文字池加载的相对于 PC 的 32 位值。通常,文字值在静态链接时受制于 R_ARM_SBREL32 类型的重定位。数据与 SB 的偏移量显然是可执行文件布局的一个属性,它在静态链接时是固定的。它不依赖于数据的加载位置,它由运行时 SB 的值捕获。
7.4 变体兼容性
标准变体(第 28 页)中描述的变体可以生成与基本标准不兼容的代码。尽管如此,仍然存在可能与多个变体兼容的代码子集。本节描述了变体之间的理论兼容性水平;但是,工具链是否必须接受编译为不同基本标准的兼容对象,或者正确拒绝不兼容的对象,是由实现定义的。
7.4.1 VFP 和基本标准兼容性
为 VFP 调用标准编译的代码与基本标准兼容(反之亦然),如果不使用浮点或容器化向量参数或结果,或者只有传递或返回的例程这些值是可变参数例程。
7.4.2 RWPI 和基本标准的兼容性
为基本标准编译的代码如果不使用寄存器r9,则与RWPI 调用标准兼容。但是,平台 ABI 可能会进一步限制有用兼容的代码子集。
7.4.3 VFP 和 RWPI 标准兼容性
VFP 调用变体和 RWPI 寻址变体可以组合以创建第三个主要变体。
上述规则的适当组合将确定代码是否兼容。
7.4.4 半精度格式兼容性
可以以 Arm Alternative 格式表示的值集不同于可以在 IEEE754-2008 格式呈现代码中表示的值集,该代码构建为使用与使用另一种格式的代码不兼容的格式。但是,大多数代码不会使用任何一种格式,因此将与两种变体兼容。
第八章ARM C 和 C++ 语言映射
本节介绍 Arm 编译器如何将 C 语言功能映射到机器级标准。
就 C++ 是 C 语言的超集而言,它还描述了 C++ 语言特性的映射。
8.1 数据类型
8.1.1 算术类型
C 算术类型到基本数据类型的映射显示在表 3,C 和 C++ 内置数据类型的映射(第 31 页)中。
表 8.1:表 3,C 和 C++ 内置数据类型的映射
wchar_t 的首选类型是 unsigned int。但是,虚拟平台可以选择使用 unsigned short 代替。平台标准必须记录其选择。
8.1.2 指针类型
指针类型的容器类型显示在表 4,指针和引用类型(第 32 页)中。 C++ 引用类型被实现为指向该类型的指针。
表 8.2:表 4,指针和引用类型
8.1.3 枚举类型
此 ABI 将枚举类型表示的选择委托给平台 ABI(无论是由标准定义还是由自定义和实践定义),如果没有定义的平台 ABI,则委托给接口契约。
两种允许的 ABI 变体是:
• 枚举类型通常占用一个字(int 或 unsigned int)。如果一个字不能表示其所有枚举值,则该类型占用一个双字(long long 或 unsigned long long)。
• 枚举类型的存储容器的类型是可以包含其所有枚举值的最小整数类型。
当整数类型的有符号和无符号版本都可以表示所有值时,此 ABI 建议应首选无符号类型(符合惯例)。
讨论
C 和 C++ 语言标准中对枚举类型的定义没有定义二进制接口,并留下了以下问题。
• 枚举类型的容器是否具有固定大小(如在大多数操作系统环境中所期望的那样),或者大小是否不大于容纳枚举值所需的大小(如大多数嵌入式用户所期望的那样)?
• 当一个(严格来说,不符合标准的)枚举值(例如 MAXINT+1)溢出一个固定大小(例如 int)的容器时会发生什么?
• 枚举类型的值(在C/C++ 要求的任何转换之后)是有符号的还是无符号的?
关于最后一个问题,C 和 C++ 语言标准规定:
• [C] 每个枚举类型都应与整数类型兼容。类型的选择是实现定义的,但应该能够表示枚举的所有成员的值。
• [C++] 枚举类型不是整数类型,而是 。 . .的右值。 . .枚举类型 (7.2) 可以转换为以下第一种类型的右值,该类型可以表示其基础类型的所有值:int、unsigned int、long 或 unsigned long。
在此 ABI 下,这些语句允许描述可移植二进制包接口的头文件以可移植、严格一致的方式强制其客户端采用 32 位有符号 (int/long) 表示的枚举值类型(通过定义一个负数、一个正数,并确保枚举数的范围超过 16 位但不超过 32)。
否则,必须通过诉诸平台 ABI 或单独的接口合约来建立对二进制表示的共同解释。
8.1.4 附加类型
C 和 C++ 都要求系统提供附加类型定义,这些类型定义是根据基本类型定义的。通常这些类型是通过包含适当的头文件来定义的。但是,在 C++ 中,可以在不使用任何头文件的情况下仅通过使用 ::operator new() 来公开 size_t 的底层类型,并且 va_list 的定义对编译器中的内部实现有影响。符合 AAPCS 的对象必须使用表 5,附加数据类型(第 33 页)中显示的定义。
表 8.3:表 5,其他数据类型
8.1.5 易失性数据类型
数据类型声明可以使用易失性类型限定符进行限定。编译器可能不会删除对 volatile 数据类型的任何访问,除非它可以证明包含访问的代码永远不会被执行;但是,编译器可能会忽略自动变量的 volatile 限定,除非函数调用 setjmp(),否则该变量的地址永远不会被占用。结构或联合上的 volatile 限定应被解释为将限定递归地应用于组成它的每个基本数据类型。必须始终通过访问整个类型来访问 volatile 限定的基本数据类型。
对包含 volatile 限定成员的整个结构或联合进行赋值或从其中赋值的行为是未定义的。同样,如果使用强制转换来更改类型的限定或大小,则行为是未定义的。
并非所有 Arm 架构都提供对所有宽度类型的访问;例如,在 Arm 架构 4 之前,没有访问 16 位数量的指令,类似的问题也适用于访问 64 位数量。此外,处理器底层的存储器系统可能对一些或全部存储器具有受限的总线宽度。在这些情况下,适用于 volatile 类型的唯一保证是,对于上面规定的每次访问,该类型的每个字节都应仅访问一次,并且不应访问包含该类型之外的 volatile 数据的任何字节。然而,如果编译器有一条可用的指令可以准确地访问该类型,它应该优先使用它而不是更小或更大的访问。
8.1.6 结构、联合和类布局
结构和联合是根据组成它们的基本数据类型进行布局的(参见复合类型(第 15 页))。所有成员都按声明顺序排列。 [CPPABI] 和 [GCPPABI] 中描述了适用于 C++ 非 POD 类布局的附加规则。
8.1.7 位域
位域可以有任何整数类型(包括枚举和布尔类型)。
位域序列按照使用以下规则声明的顺序排列。
对于每个位域,其容器的类型为:
• 如果其大小不大于其声明类型的大小,则其声明类型。
• 如果其大小大于其声明类型的大小,则最大的整数类型不大于其大小(请参阅过大的位域(第 36 页))。
容器类型以与该类型的普通(非位域)成员相同的方式有助于包含聚合的对齐,对于零大小或匿名位域无例外。
注意:C++ 标准规定匿名位域不是成员,因此不清楚非零大小的匿名位域是否应该有助于聚合的对齐。在这个 ABI 下它确实如此。
每个位域的内容都包含在其容器类型的一个实例中。
最初,我们定义不大于其容器类型的字段的布局。
不大于其容器的位域
令 F 为我们希望确定其地址的位域。我们将容器地址 CA(F) 定义为字节地址
此地址将始终处于容器类型的自然对齐位置,即
容器内 F 的位偏移量 K(F) 以与字节序相关的方式定义:
• 对于大端数据类型,K(F) 是从容器的最高有效位到位域最高有效位的偏移量。
• 对于little-endian 数据类型,K(F) 是从容器的最低有效位到位域的最低有效位的偏移量。
可以通过加载其容器、移位和屏蔽取决于字节顺序、K(F)、容器大小和字段宽度的量来提取位字段,然后在需要时进行符号扩展。
F 的位地址 BA(F) 现在可以定义为
对于落入宽度为 C 且对齐方式为 A (≤ C) 的容器中的位地址 BA(均以位表示),将未分配容器位 (UCB) 定义为
我们进一步定义截断函数
TRUNCATE(X,Y) = Y * ⌊X/Y⌋
即Y不大于X的最大整数倍。
我们现在可以定义下一个容器位地址(NCBA),当当前容器中没有足够的空间来保存下一个位字段时将使用它
在布置位域序列的每个阶段,都有:
• 当前位地址 (CBA)
• 容器大小 C 和对齐方式 A,由即将布置的域类型确定( 8, 16, 32, . . .)
• 场宽,W (≤ C)。
对于每个位域 F,按照声明顺序,布局由以下确定
1. 如果域宽度 W 为零,则设置 CBA = NCBA(CBA, A)
2. 如果 W > UCB(CBA, C, A) , 设置 CBA = NCBA(CBA, A)
3. 分配 BA(F) = CBA
4. 设置 CBA = CBA + W。
注意:AAPCS 不允许导出的接口包含压缩结构或位域。然而,可以通过将上述规则中的对齐 A 降低到低于自然容器类型的对齐来实现对打包位域进行布局的方案。在这些情况下,ARMCC 使用 A=8 的对齐方式,但 GCC 使用 A=1 的对齐方式。
位域提取表达式
要在位地址 BA(F) 处访问宽度为 W 且容器宽度为 C 的字段 F:
• 在字节地址 TRUNCATE(BA(F), C) 处加载(自然对齐的)容器/ 8 到一个寄存器 R(或两个寄存器,如果容器是 64 位)
• 设置 Q = MAX(32, C)
• 小端,设置 R = (R <> (Q – W)。
• 大端,设置 R = (R <> (Q – W)。
long long 位域对 64 位量使用移位操作;通常情况下,这些表达式可以简化为对单个 32 位数量使用操作(但请参阅易失性位字段 – 保留容器访问的数量和宽度(第 36 页))。
过大的位域
C++ 允许位域的宽度规范超过容器大小,分配规则在 [GCPPABI] 中给出。使用上述符号,为宽度为 C 且对齐方式为 A 的容器分配宽度为 W 的过大位字段是通过以下方式实现的:
• 选择一个新的容器宽度 C’,它是基本整数数据的宽度最大尺寸小于或等于 W 的类型。此容器的对齐方式将为 A’。请注意,C’ >= C 和 A’ >= A。
• 如果 C’ > UCB(CBA, C’, A’) 设置 CBA = NCBA(CBA, A’)。这确保了位域将被放置在下一个容器类型的开头。
• 使用(W,C,A)的值(C,C’,A’)分配一个正常(过小)位域。
• 设置 CBA = CBA + W – C。
注意:虽然标准 C++ 没有 long long 数据类型,但这是该语言的常见扩展。为了避免这种类型的存在改变了超大位域的布局,上述规则是根据基本机器类型(基本数据类型(第 14 页))来描述的,其中始终存在 64 位整数数据类型。
一个超大的位域可以通过访问它的容器类型来访问。
组合位域和非位域成员
位域容器可以与非位域成员重叠。为了确定位域成员的布局,CBA 将是前一个非位域类型之后的第一个未分配位的地址。
注意:添加到紧接在位域成员之前的结构的任何尾部填充都是结构的一部分,在确定 CBA 时必须考虑在内。
当非位域成员跟随位域时,它被放置在分配的位域之后的最低可接受地址。
注意:在布局基本数据类型时,可以将它们全部视为宽度等于容器大小的位域。然后可以应用不大于其容器(第 34 页)的位域中的规则来确定结构内的精确地址。
易失性位域——保留容器访问的数量和宽度
当读取易失性位域并且其容器不与任何非位域成员或任何零长度位域成员重叠时,必须准确读取其容器一旦使用适合容器类型的访问宽度。
当一个 volatile 位域被写入,并且它的容器不与任何非位域成员或任何零长度位域成员重叠时,它的容器必须使用适合于容器的类型。这两个访问不是原子的。
注意:此 ABI 不对位域的访问宽度施加任何限制,其中容器与非位域成员重叠,或者容器与放置在两个其他位域之间的任何零长度位域重叠。这是因为 C/C++ 内存模型将它们定义为独立的内存位置,两个线程可以同时访问这些位置。为此原因,必须允许编译器使用更窄的内存访问宽度(包括将访问分成多条指令)以避免写入不同的内存位置。例如,在 struct S { int a:24;字符 b; }; a 写入 a 一定不能同时写入 b 占用的位置,这在所有当前 Arm 架构中都需要至少两次内存访问。同样,在 struct S { int a:24;整数:0;诠释 b:8; };,写入 a 或 b 不得相互覆盖。
不能合并对同一易失性位字段或同一容器内的其他易失性位字段的多次访问。例如,易失性位字段的增量必须始终实现为两次读取和一次写入。
注意:请注意,即使位域的宽度和对齐方式暗示使用更窄的类型可以更有效地实现访问,易失性访问规则也适用。对于写入操作,即使容器的全部内容将被替换,读取也必须始终发生。
如果两个易失性位域的容器重叠,则访问一个位域将导致访问另一个位域。例如,在 struct S {volatile int a:8;易失性字符 b:2};对 a 的访问也会导致对 b 的访问,但反之则不然。
如果非易失性位字段的容器与易失性位字段重叠,则未定义访问非易失性字段是否会导致访问易失性字段。
8.2 参数传递约定
子程序调用的参数列表是通过按指定顺序获取用户参数而形成的。
• 对于C,每个参数都由源代码中指定的值构成,但数组是通过传递其第一个元素的地址来传递的。
• 对于 C++,隐式 this 参数作为额外参数传递,该参数紧接在第一个用户参数之前。 CPPABI 中描述了编组 C++ 参数的其他规则。
• 对于可变参数函数,匹配省略号 (…) 的浮点参数将转换为 double 类型。
然后根据过程调用的标准规则(参见参数传递(第 24 页))或适当的变体来处理参数列表。
第九章高级 SIMD 扩展和 MVE 的附录支持
9.1 介绍
Arm 架构的高级 SIMD 和 M-profile 矢量扩展增加了对处理短矢量的支持。因为 C 和 C++ 语言不提供标准类型来表示这些向量,所以对它们的访问由供应商扩展提供。本附录的状态是关于公共二进制接口的规范,即使用这些类型的函数的调用约定和名称修改。在其他方面,它提供了丰富的信息。
9.2 SIMD 向量数据类型
SIMD 向量数据类型的访问是通过包含以下两个头文件之一获得:arm_neon.h、arm_mve.h。这些头文件提供以下特性:
• 它们提供一组映射到短向量类型的用户级类型名称
• 它们为分别映射到高级 SIMD 和 M-profile Vector Extension (MVE) 指令集的内在函数提供原型。
注意:内在函数超出了本规范的范围。用户级别类型的使用细节(例如初始化和自动转换)也超出了本规范的范围。有关详细信息,请参阅 [ACLE]。
注意:用户级类型列于表 6:仅使用 64 位容器化向量的高级 SIMD 扩展向量数据类型(第 39 页)和表 7:使用 128 位容器化向量的 SIMD 向量数据类型(第 40 页)。这些类型具有 64 位对齐并直接映射到容器化向量基本数据类型。容器化向量的内存格式定义为使用填充操作从基本类型的数组中加载指定的寄存器,然后使用加载的 64 位 (D) 寄存器的单个 VSTM 将该值存储到内存中。
MVE 只允许 128 位向量类型,它使用无符号整数向量来表示多项式。
这些表还列出了用于名称修饰的等效结构类型。这些类型是否由实现实际定义是未指定的。
表 9.1:表 6:仅使用 64 位容器化向量的高级 SIMD 扩展向量数据类型
表 9.2:表 7:使用 128 位容器化向量的 SIMD 向量数据类型
9.2.1 C++ 重整
对于 C++,重整的参数名称就像使用了等效的类型名称一样。例如,
被破坏为