1. 程式人生 > >【arm】arm32位和arm64位架構、暫存器和指令差異分析總結

【arm】arm32位和arm64位架構、暫存器和指令差異分析總結

Date: 2018.9.21

1、參考
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. 對向量中每個元素進行水平方向的算術運算,併產生一個標量結果。

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

  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
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的方式為:
    使用控制流指令,如條件跳轉、無條件跳轉、異常生成和異常返回指令。

THE END!