ARM V8A体系结构-第八章 移植到A64

概述

本章并不打算作为为所有系统编写可移植代码的详尽指南,但是,本章应涵盖应用工程师在ARM特定机器上进行代码移植时应了解的主要领域。
在将代码从A32和T32指令集移动到AArch64中的A64指令集时,您应该注意一些明显的差异:

  • A32指令集中的大多数指令都可以有条件地执行。也就是说,可以将条件代码附加到指令,并基于先前标志设置指令的结果执行(或不执行)指令。尽管这使得编程技巧能够减少代码大小和周期计数,但这会使高性能处理器的设计变得非常复杂,并且会导致无序执行。
    操作码字段中保留的用于表示预测的必要位可以有效地用于其他目的(例如,提供从更大的通用寄存器池中进行选择的空间)。因此,在A64代码中,只有一小部分指令可以有条件地执行,而一些比较和选择操作取决于条件。参见第6-8页的条件说明。
  • 许多A64指令可以对源寄存器或仅受操作数大小限制的寄存器应用任意常量移位。此外,A64还提供了非常有用的扩展寄存器表。有清晰直接的指令来处理更复杂的情况,如变量移位。
    T32也比A32限制性更大,因此在某些方面A64是保留了一些限制性。A32的Operand2在A64中并不存在,但各个指令类都有自己的操作
  • 加载和存储指令的可用寻址模式有一些更改。A32和T32的偏移、预索引表和后索引表在A64中仍然可用。有一种新的PC相对寻址模式,因为PC不能以与通用寄存器相同的方式访问。A64加载可以内联移位寄存器(尽管不如A32灵活),并且它们也可以使用一些扩展模式(例如,您可以使用32位数组索引)
  • A64从以前的ARM体系结构中删除了所有多内存访问指令(加载或存储多个指令),这些指令能够从内存中读取或写入任意寄存器列表。改用可在任意两个寄存器上操作的加载对(LDP)和存储对(STP)指令。PUSH和POP指令也已移除
  • ARMv8增加了加载和存储指令,其中包括一个单向内存屏障:加载获取和存储释放。这些在ARMv8 A32和T32以及A64中提供。加载获取指令要求任何后续内存访问(按程序顺序)仅在加载获取之后可见。存储释放确保在存储释放变为可见之前,所有早期的内存访问都可见。参见第6-18页的记忆屏障和围栏说明。
  • AArch64不支持协处理器的概念,包括CP15。新的系统指令允许访问通过AArch32中的CP15协处理器指令访问的寄存器。
  • AArch64中没有CPSR单个寄存器存在。相反,PSTATE字段(如NZCV)可以使用专用寄存器访问。

对于许多应用程序,将旧版本的ARM体系结构或其他处理器体系结构的代码移植到A64意味着只需重新编译源代码。然而,在许多领域C代码并不是完全可移植的。
A64和A32/T32之间的相似性在以下示例中说明。下面的三个序列显示了一个简单的C函数和输出代码,先是T32,然后是A64。两者之间的对应关系很容易看出。
在这里插入图片描述
A64提供的通用功能是从A32和T32中发展而来的,因此在两者之间移植代码相当简单。将A32汇编代码转换为A64通常也很简单。大多数指令很容易在这些指令集之间映射,A64中的许多序列变得更简单。

1、对齐

数据和代码必须与适当的边界对齐。访问的对齐可能会影响ARM内核的性能,并且在将代码从早期体系结构移动到ARMv8-a时可能会出现可移植性问题。出于性能原因,或者在移植指针、32位、64位整数变量做出假设的代码时,需要注意对齐问题。
ARM编译器的早期版本提供了ALIGN n指令,其中n以字节为单位指定对齐边界。例如,指令ALIGN 128将地址与128字节边界对齐。
GNU组件语法(ARM Complier 6语法)提供了 .balign n指令,使用与ALIGN相同的格式。
GNU语法程序集还提供了 .align n指令,但n的格式因系统而异。balig n指令提供与相同的对齐功能,并且在所有体系结构中保持一致的行为。
应该将ALIGN n的所有实例转换为.balign n 当n从较旧的编译器迁移到ARM编译器6。

2、数据类型

在64位机器上的许多C和C派生语言编程环境中,int变量仍然是32位宽,但长整型和指针是64位宽。它们被称为LP64数据模型。本章假设LP64,尽管有其他数据模型可用,请参见第5-7页的表5-1。
ARM ABI为LP64定义了许多基本数据类型。其中一些可能因体系结构而异,包括在以下内容中:
在这里插入图片描述
a:取决于环境。在基于GNU的系统(如Linux)中,此类型始终为32位。
b:如果枚举类型中的值集不能使用int或unsigned int作为容器类型表示,并且语言允许扩展枚举集,则可以使用long-long或unsigned-long容器

当将AArch64与ARM体系结构的早期版本进行比较时,由于64位通用寄存器和操作,64位数据类型通常可以更有效地处理。int仍然是32位的,可以通过通用寄存器(W寄存器)的可用32位视图有效地处理它。然而,指针是指向数据或代码的64位地址。ARM ABI将char定义为默认无符号。对于体系结构的早期版本也是如此。

如果代码不以不可移植的方式操作指针,例如向非指针类型转换或从非指针类型转换或执行指针算术,那么移植就会简化。这意味着您从未在int变量中存储过指针(intptr_t和uintptr_t可能除外),也从未将指针强制转换为int。有关这方面的更多信息,请参阅第8-8页的将代码从32位环境移植到64位环境时的问题。
除其他影响外,这会更改大小,可能还会更改结构和参数列表的对齐方式。使用stdint.h中的int32_t和int64_t类型。请注意,在AAPCS64-LP64中,size_t和ssize_t都是64位的。
出于性能原因,编译器会尝试在自然大小边界上对齐数据。大多数编译器试图优化编译模块中全局数据的布局。
AArch64支持16、32、64和128位数据未对齐访问,其中使用的地址不是要加载或存储的数量的倍数。
但是,高级加载或存储以及加载获取或存储释放指令只能访问对齐的地址。这意味着用于构造信号量和其他锁定机制的变量通常必须对齐。
在正常情况下,所有变量都应该对齐。在大多数情况下,未对齐访问的平均效率仍然低于对齐访问
相对于系统中的其他CPU或总线主机,未对齐的访问永远不能保证是原子的。此规则唯一的主要例外是访问打包数据结构——这可以在通过文件或网络连接等方式向外部世界封送数据或从外部世界封送数据时节省大量工作。
与对齐的访问相比,未对齐的访问可能会影响性能。在自然大小边界上对齐的数据访问效率更高,而未对齐的访问可能需要额外的总线或缓存周期。
attribute((packed, aligned(1)) 指令应用于警告编译器潜在的未对齐访问,例如在手动转换指向不同数据类型的指针时。

2.1 汇编代码

许多A32汇编指令可以很容易地替换为类似的A64指令。不幸的是,没有自动机制,但是可以通过简单的翻译来实现,下边列举了A32/T32和A64的指令对照表:
在这里插入图片描述
在这里插入图片描述
但是,在也在一些地方存在差异需要重写。下表显示了其中一些:
在这里插入图片描述
64位APCS需要128位(16字节)堆栈对齐

表8-4显示了PSTATE中的命名字段如何替换CPSR:
在这里插入图片描述
T32条件执行方案按照第8-6页表8-4中A32列所示的顺序编译。在A64中,它使用新的条件选择指令,如A64列所示。
以下示例说明了两个指令集(T32和A64)中条件执行之间的差异:
在这里插入图片描述

3、将代码从32位环境移植到64位环境时出现的问题

将C代码迁移到64位环境中运行时,可能会出现一些常见问题。这些不是特定于ARM的。

  • 注意指针和整数,因为它们可能大小不同。ARM建议使用stdint.h中uintptr_t、intptr_t用于指针类型转换为整数值。指针算法中使用的偏移量应声明为ptrdiff_t,因为使用int可能会产生不正确的结果
  • 64位系统具有更大的潜在内存范围,32位int可能不足以索引数组中的所有项
  • C表达式中的隐式类型转换可能会产生一些意外的效果。注意确保使用的任何常量值与掩码本身的类型相同
  • 使用不同长度或符号的数据类型执行操作时要小心。例如,当表达式中混合了无符号整数和有符号32位整数,并且将结果指定给有符号长整数时,可能需要将其中一个操作数显式转换为其64位类型,这将导致所有其他操作数也提升为64位。注意,在A64(LP64)上,long通常是64位类型。

3.1 重新编译或重写代码

任何移植都不可避免地需要重新编译和重写代码的元素。在大多数情况下,目标是最大化前者,最小化后者。
好消息是许多代码只是重新编译。然而,由于许多基本类型的大小将发生变化,因此应谨慎行事。尽管编写良好的C代码不应该对单个类型的大小有太多依赖性,但您可能会遇到一些。
因此,最佳实践必须是在重新编译时启用所有警告和错误,并确保注意到编译器发出的任何警告,即使代码似乎编译时没有错误。
请密切注意代码中的任何显式类型转换,因为当底层类型的大小发生变化时,这些类型转换通常是错误的来源。

3.2 ARM Compiler 6 options for ARMv8-A

为编译器提供正确的选项以允许代码生成ARMv8-A目标非常重要,以下是可用的选项:–target 为指定的目标生成代码。–target选项是必需的,没有默认值必须始终指定目标体系结构
语法如下:–target=triple
triple是来自architecture-vendor-OS-abi,如下:

  • The AArch64 state of the ARMv8-A architecture:aarch64-arm-none-eabi
  • The AArch32 state of the ARMv8-A architecture:armv8a-arm-none-eabi
  • The ARMv7-A architecture:armv7a-arm-none-eabi

例如:–target=armv8a-arm-none-eabi

注:–target选项是一个armclang选项。对于所有其他工具,如armasm和armlink,使用–cpu和–fpu选项指定目标处理器和体系结构。

使用–mcpu选项为特定ARM处理器启用代码生成,如:
在这里插入图片描述
编译AArch32的代码与编译ARMv7-A的代码非常相似。尽管AArch32有一些新的指令(如Load Acquire和Store Release),并且SWP指令已被删除,但这些指令通常不是由编译器生成的。
使用+nosimd选项编译可避免使用NEON/浮点指令或寄存器。这对于NEON未通电的系统或特定代码段(例如重置代码和异常处理程序)可能很有用,其中确保不使用NEON/浮点非常重要。默认情况下,不使用加密扩展,但使用NEON。

4、推荐新C代码

  • 使用sizeof()而不是常量,例如:(void**) calloc(4,100)修改为(void**) calloc(sizeof(void ), 100)或void a;(void) calloc(sizeof(a), 100);
  • 若需要有直接的类型。使用stdint.h中的类型
  • 如果需要将指针强制转换为整数,请使用保证能够保存它的类型,例如uintptr_t。
  • 如果数据大小和布局很重要,请在排列结构体成员时注意。例如,代码:struct { void *a; int b; int c} bob优于struct { int b; void *a; int c;},与AAPCS64中一样,元素a在其前面插入了32位的填充,以使其保持64位对齐的。
  • 使用size_t
  • 使用limit.h在适当情况下,在获取数据类型时要小心
  • 对所使用的类型使用适当的函数/宏/内置。例如:用long atol(char *)代替 int atoi(char *)
  • 使用原子操作时,请使用正确的64位函数对64位类型执行这些操作
  • 不要假设对同一结构中不同位字段的操作是独立的-在64位平台上可以读写的位比在32位平台上更多
  • 带L的后缀数字在32位编译时为32位,在64位编译时为64位。这将确保它们与长类型匹配:long value = 1L << SOMANY;对于32位和64位编译器上的64位数字,请使用带LL或ULL的后缀
  • 或者,您可以使用stdint.h提供C99的宏。(例如,INT64_C和UINT64_C),它允许在不使用L和LL显式后固定的情况下定义数字。例如size_t value = UINT64_C(1) << SOMANY;

4.1 显式和隐式类型转换

当表达式中混合了不同长度and/or有符号的数据类型时,C/C++中的内部提升和类型转换可能会导致一些意外问题。特别是,有时理解在表达式求值时在什么点进行转换是很重要的。
例如:
int + long => long;
unsigned int + signed int => unsigned int
int64_t + uint32_t => int64_t

如果符号丢失转换是在提升到long之前执行的,则当分配给有符号long时,结果可能不正确。这将导致其他操作数提升为64位,并且在指定表达式时不需要进一步转换。另一种解决方案是强制转换整个表达式,以便在赋值时进行符号扩展。然而,对于这些问题并没有一刀切的解决方案。在实践中,修复它们的最佳方法是理解代码试图做什么。

考虑这个例子,在这个示例中,您可以预期结果为1:
在这里插入图片描述
在32位中long a = -1(0XFFFFFFFF),在64位中a = 0x00000000FFFFFFFF。
显然,这是一个意想不到的、非常错误的结果!是因为b在加法(以匹配)之前转换为无符号整数 c) ,因此加法的结果是一个无符号整数。
一种可能的解决方案是在添加之前转换为较长的类型:
在这里插入图片描述

4.2 位变换操作

注意确保位掩码的宽度正确。C表达式中的隐式类型转换可能会产生一些意想不到的效果。考虑以下功能:在64位变量中设置指定位:
在这里插入图片描述

此函数在32位环境中工作正常,允许设置位[31:0]。要将其移植到64位系统,您可能认为更改掩码类型以允许设置位[63:0]就足够了,如下所示:
在这里插入图片描述
同样,这无法正常工作,因为数值1具有int类型。具体行为取决于单个编译器的配置和假设。
应该为下边这种写法:
在这里插入图片描述
如果您需要一个特定大小的整数,请使用诸如uint32_t和UINT32_C宏系列之类的类型,这些类型在stdint.h中定义。

4.3 索引

在64位环境中使用大型数组或对象时,请注意int可能不再足够大,无法索引所有条目。特别是,在使用int索引对数组进行迭代时要小心。
在这里插入图片描述
由于size_t是64位类型,unsigned int是32位类型,因此可以用来定义对象的大小以使循环永不终止。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值