arm32位和arm64位架构、寄存器和指令差异分析总结

1、参考

https://blog.csdn.net/SoaringLee_fighting/article/details/82155608
https://blog.csdn.net/SoaringLee_fighting/article/details/81743505
https://blog.csdn.net/SoaringLee_fighting/article/details/81906495
https://blog.csdn.net/SoaringLee_fighting/article/details/82530435

2、前言

  最近三个月的时间,都在进行解码库的arm架构汇编优化,包括arm32位汇编优化和arm64位汇编优化。在arm32位入门之后,只要掌握了两种架构的寄存器和指令集差异之后,就可以很快上手编写arm64位汇编代码了。下面就arm32位和arm64位架构、寄存器和指令差异进行分析总结。

3、架构差异

  ARM是RISC(精简指令集)处理器,不同于x86指令集(CISC,复杂指令集)。
  Arm32位是ARMV7架构,32位的,对应处理器为Cortex-A15等; iphone5以前均是32位的;
需要注意:ARMV7-A和ARMV7-R系列支持neon指令集,ARMv7-M系列不支持neon指令集。
  ARM64位采用ARMv8架构,64位操作长度,对应处理器有Cortex-A53、Cortex-A57、Cortex-A73、iphones的A7和A8等,苹果手机从iphone 5s开始使用64位的处理器。

4、寄存器差异

4.1 ARM通用寄存器

ARM32位通用寄存器和ARM64位通用寄存器差异详见:ARM寄存器及其说明

4.2 NEON寄存器

ARM32位neon寄存器和ARM64位neon寄存器差异:
32位下 NEON寄存器:
包括:

  • 32个S寄存器,S0~S31,(单字,32bit)
  • 32个D寄存器,D0~D31,(双字,64bit)
  • 16个Q寄存器,Q0~Q15,(四字,128bit)
    在这里插入图片描述

使用注意:
1、NEON寄存器将每个寄存器均视为一个向量,该向量又包含1,2,4,8或16个大小和类型均相同的元素。也可以将各个元素当做标量访问。
NEON的这三种寄存器是重叠的,物理地址是一样的。
2、NEON寄存器在使用时,如果用到d8~d15寄存器,需要先入栈保存vpush {d8-d15},使用完之后要出栈vpop {d8-d15}。

64位下NEON寄存器:
包括:

  • 32个B寄存器(B0~B31),8bit
  • 32个H寄存器(H0~H31),半字 16bit
  • 32个S寄存器(S0~S31) ,单字 32bit
  • 32个D寄存器(D0~D31),双字 64bit
  • 32个Q寄存器(V0~V31),四字 128bit

不同位数下寄存器之间的关系如下图所示:
这里写图片描述

其中S0是D0的低半部分,D0是V0的低半部分 。

注意:
64位下NEON寄存器与32位下NEON寄存器之间的关系不同!
neon寄存器 v0~v31使用说明:
v0~v7:用于参数传递和返回值,子程序不需要保存;
v8~v15:子程序调用时必须入栈保存(低64位);
v16~v31:子程序使用时不需要保存。
具体可参考:
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf 5.1.2 SIMD and Floating-Point Registers

5、A64指令集特有的指令及其用法(指令差异)

AARCH64是全新32位固定长度指令集,支持64位操作数的新指令,大多数指令可以具有32位或64位参数。

  • 32位neon指令都是以V开头,而64位neon指令没有V;
  • 32位寄存器需要保存的是r4~r11,q4~q7,而64位寄存器需要保存的是x19~x29 , v8~v15;
  • 64位下NEON寄存器与32位下NEON寄存器之间的关系不同,32位下d寄存器和q寄存器是重叠映射的,而64下的d寄存器和v寄存器是一一对应的,具体详见4.1;
  • 向64位或者更低位的矢量寄存器中写数据时,会将高位置零;
  • 在AArch64中,没有通用寄存器的SIMD和饱和算法指令。只有neon寄存器才有SIMD或饱和算法指令;
  • ARM64下没有swap指令和条件执行指令。
  • 关于指令中立即数取值范围的说明:
    不同指令中的#<imm>或者#<const>具有不同的取值范围,这个取决于所使用的指令,比如:
mov		<wd>, #<imm>  //该指令中立即数范围为-65536~65535。
cmp	    <wn>, #<imm>  //该指令中#<imm>为无符号立即数,取值范围为0~4095(12 bit)。

特别说明:大部分ARM指令中的立即数不能是负数,需要注意不同指令的取值范围。

  1. shl和ushr指令

	shl  <V>.<d>, <V>.<n>, #<shift>
	ushr  <V>.<d>, <V>.<n>, #<shift>
	ushr  d2, d2,  #8

使用注意事项:这两条指令只能操作64位数据,即只能对D寄存器进行处理。
ushr最多只能进行64位数据的右移,并且右移时会影响V2寄存器的高64位数据(清零),因此高64位数据需要在右移前保存,否则相关数据会被修改。

  2. INS指令
用法与MOV指令基本一样,可以实现neon标量与neon标量之间的传送,以及ARM寄存器与neon标量之间的传送。

INS   <Vd>.<Ts>[index1], <Vn>.<Ts>[index2]
INS   <Vd>.<Ts>[index1], Rn

  3. SUQADD、USQADD指令
既有标量用法,也有矢量用法。

SUQADD <V><d>, <V><d>     // signed saturating accumulate of unsigned value
SUQADD <Vd>.<T>, <Vn>.<T>

USQADD <V><d>, <V><d>    // unsigned saturating accumulate of signed value
USQADD <Vd>.<T>, <Vn>.<T>

  4. RBIT、REV指令

 RBIT <Wd>, <Wn> //reverse bits
 REV <Wd>, <Wn>  //reverse bytes

 5. ADDV,SADDLV,SMAXV,SMINV (Vector Reduce(across lanes))
后缀带V指令:

ADDV <V><d>, <Vn><T>    // Integer sum element to scalar(vector)
SADDLV <V><d>, <Vn><T>  // Signed Interger sum elements to long scalar(vector)
SMAXV <V><d>, <Vn><T>   // Signed Interger maximum elements to scalar(vector)
SMINV <V><d>, <Vn><T>   // Signed Interger minimum elements to scalar(vector)

eg.:
addv B0, v1.8B			// 将v1寄存器中的低64位中8个8位数据相加求和后,赋给v0的最低8位。
addv H2, v1.8B			// 将v1寄存器中的低64位中8个字节依次相加求和,并将结果赋给v1第2个16位标量中。

用法说明:
vector reduce instrunctions perform arithmetic operations horizontally, that is across all lanes of the input vector. they deliver a single scalar result. 对矢量中每个元素进行水平方向的算术运算,并产生一个标量结果。

更多详细解释可以参考:https://static.docs.arm.com/ddi0487/a/DDI0487A_j_armv8_arm.pdf
这里写图片描述
 6. sxtw使用注意事项
负数在使用时必须进行符号扩展!
比如:

sxtw   x4, w4

  7. w寄存器到v寄存器
直接使用dup指令

dup		v0.8B,  w2

  8.常用指令对应关系(arm32---->arm64)

 vmovl------>uxtl/sxtl
 vqmovn----->sqxtn
 vqmovun----->sqxtun
 vqrshrun---->sqrshrun
 vceq------->cmeq
 vcge------->cmge
 vadd------>add
 vsub------>sub
 vaddl----->saddl,uaddl 
 vaddw----->saddw,uaddw,sw2addw2,uadd
 vmull----->smull,smull2,umull,umull2
 vmax,vmin----->smax,umax,smin,umin
 vmlal--------> smlal,smlal2,umlal,umlal2
 vrshl--------> urshl,srshl
 vtrn---------> trn1,trn2
 vstm/vstr----> stp/str
 vld1.32 {d0[]}, [r0], r2-----> ld1r {v0.S}[0], [x0], x2
 addgt,addle,subgt,suble----->csel,csetm,cset,csinc,csinv

更多可参考:
https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf

  9. 指令结尾带“2”的寄存器高64位操作:

 smull2 v0.4S,	v1.4H, v2.4H //将v1和v2高64位中每一个16位相乘,并将结果放在v0的每个32位中。
 sqxtn2  v5.8H, v4.4S  //将v4中的每个32位元素,饱和缩进到v5高64位的每个16位中。
 sxtl2  v16.4S, v17.4H //将v17高64位中的每个16位元素,扩展到v16的每个32位元素中。

  10. ADDP,SMAXP,SMINP,UMAXP,UMINP
后缀带P指令:

addp  v1.8B, v2.8B, v3.8B   // add pairwise

用法说明:
the SIMD pairwise arthmetic instructions perform operations on pairs of adjacent element and deliver a vector result. 对矢量中的相邻元素两两进行算术运算,并产生一个矢量结果。

  11. 替代arm32下条件执行指令的arm64位指令:

arm32位:

   bgt,addgt,suble等

arm64位:

b.gt,csinc, csel, cset, csetm,  csnev

说明: 在ARM64下没有条件执行指令。

6、AArch32与AArch64的区别

6.1 入栈和出栈:

arm64位(aarch64架构):
(1)arm寄存器入栈和出栈:

入栈:
	sub  sp, sp, #0x10
	stp  x8, x9, [sp] // 寄存器成对入栈
出栈:
	ldp  x8,  x9, [sp] 
	add  sp,  sp, #0x10 //寄存器成对出栈

原则:1、堆栈入栈和出栈后,SP指针应该保持不变(栈平衡)。2、LIFO。 3、特别注意是,从SP位置存取数据都是从低地址开始的。
(2)neon寄存器入栈和出栈:
ARM64位三种入栈出栈方法:
方法一:

stp		d8,d9, 	[sp,	#-64]!
stp		d10,d11,[sp,	#16]!
stp		d12,d13,[sp,	#16]!
stp		d14,d15,[sp,	#16]!

ldp		d14,d15,[sp],	#-16
ldp		d12,d13,[sp],	#-16
ldp		d10,d11,[sp],	#-16
ldp		d8,d9,[sp],		#64		//恢复sp位置

方法二:

stp		d8,d9, 	[sp,	#-16]!
stp		d10,d11,[sp,	#-16]!
stp		d12,d13,[sp,	#-16]!
stp		d14,d15,[sp,	#-16]!

ldp		d14,d15,[sp],	#16
ldp		d12,d13,[sp],	#16
ldp		d10,d11,[sp],	#16
ldp		d8,d9,[sp],		#16		//恢复sp位置

方法三:

.macro push_v_regs
    stp    d8,  d9,  [sp, #-16]!
    stp    d10, d11, [sp, #-16]!
    stp    d12, d13, [sp, #-16]!
    stp    d14, d15, [sp, #-16]!
.endm
.macro pop_v_regs
    ldp    d14, d15, [sp], #16
    ldp    d12, d13, [sp], #16
    ldp    d10, d11, [sp], #16
    ldp    d8,  d9,  [sp], #16
.endm

arm32位:

push	{r4-r11,  lr}
vpush	{d8-d15}

vpop	{d8-d15}
pop		{r4-r11,  pc}

6.2 4x4矩阵转置:

arm64位(aarch64架构):

.macro transpose4x4B  r0,r1,r2,r3,t4,t5,t6,t7
trn1	\t4\().8B,	\r0\().8B,	\r1\().8B
trn2	\t5\().8B,	\r0\().8B,	\r1\().8B
trn1	\t6\().8B,	\r2\().8B,	\r3\().8B
trn2	\t7\().8B,	\r2\().8B,	\r3\().8B

trn1	\r0\().8B,	\t4\().8B,	\t6\().8B
trn1	\r1\().8B,	\t5\().8B,	\t7\().8B
trn2	\r2\().8B,	\t4\().8B,	\t6\().8B
trn3	\r3\().8B,	\t5\().8B,	\t7\().8B
.endm

arm32位:

vtrn.16 q0,	q1
vtrn.8	d0,	d1
vtrn.8	d2,	d3

6.3 Difference between AArch64 and AArch32

这里写图片描述
这里写图片描述
来源参考:https://www.nxp.com/docs/en/application-note/AN12212.pdf

6.4 加载数据和存储数据差异

ARM32位存取数据的格式:
vld1加载和vst1存储:

vld1.8 {d0,d1} , [r1], r2  // 将r1地址里面的连续的128bits数据依次赋给d0和d1,然后r1+r2。这里的.8表示以8bit为单位。 
vld1.16 {d0[],d1[]}, [r0:16] //这里d0和d1中的数据相同,将地址r0中取4个16位数据加载到d0和d1中。
vst1.8  {d0,d1}, [r2], r3  //将d0和d1中32个bytes数据存储到r2地址处。

ARM64位存取数据的格式:

ld1 {v20.8H, v21.8H}, [x1]  // 从x1指向的存储单元位置一次性加载128*2位数据到v20和v21中
ld1 {v1.8B},	[x1],	x2  // 从x1指向的存储单元位置加载64位数据到v1的低64位中,然后x1=x1+x2
ld1	{v18.S}[0],	[x0],	x1  // 将x0地址里面的数据取32位加载到v18的最低32位,然后x0=x0+x1
ld1r {v30.8H},	[x1]	    // 从x1地址中以16位为单位取128位加载到v30中。

st1	{v30.8H},	[x1],	#16	// 将 寄存器v30中128位数据存储到x1地址处,然后x1=x1+16
st1	{v0.S}[0],	[x0],	x2	// 将 寄存器v0的低32位数据存储到x0地址处吗,然后x0=x0+x2

6.5 补充知识

  1. 关于调用规则和SP
    ARM32位的调用规则遵循ATPCS(The ARM-THUMB Procedure Call Standarfd)调用规则,从2007年开始ARM公司正式推出AAPCS(Procedure Call Standard for the ARM Architecture)标准,是ATPCS的改进版,目前两者都是可用的标准。
    AAPCS调用规则规定,栈任何时候必须4字节对齐,在调用入口需8字节对齐。
    ARM64位下,SP在公共接口或访问内存时均必须保证16字节对齐,这是硬件要求的。
  2. 指令编码长度
    不管是aarch32还是aarch64,指令在arm指令集模式下,指令固定编码长度为32bit。
  3. 获取标签地址的方法
    arm32位下可以使用adr指令、adrl(伪指令)和ldr指令获取标签地址,需要注意使用adr、adrl指令的标签要在同一代码节中,ldr可以在不同节中。
    arm64位下,可以使用adr和adrp采用相对寻址的方式来实现,64位下不支持伪指令adrl。
  4. EL0没有权限进行AArch64和AArch32状态切换。因此A64指令无法切换到A32和T16指令。因此ARM64位指令无法在ARM32位设备上运行。
    疑问: Android 64位指令可以在32位设备上运行?
  5. 关于PC
    ARM32位下:PC=当前指令地址+8,具体跟arm处理器多级流水线机制相关,不可用作通用寄存器。
    ARM64位下:PC不能直接访问,但可以通过伪指令间接使用。需要注意的是:与32位不同的是,64位下当通过指令读取PC相对地址时,其值即为当前指令的地址。
    (1). 64位可读取PC值的情况有:
    计算相对地址,如adr,adrp,文字池加载以及直接分支;
    子程序返回地址,比如bl,blr
    (2). 可修改pc的方式为:
    使用控制流指令,如条件跳转、无条件跳转、异常生成和异常返回指令。

 

GCC下32位与64位机器类型变量所占字节数

在C语言中,编译器一般根据自身硬件针对类型变量来选择合适的字节大小,下面列举一下在GCC编译器下32位机器与64位机器各个类型变量所占字节数目:

C语言32位机器64位机器
char11
short int22
int44
long int48
long long int48
指针类型*p48
float44
double88
  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值