A64指令集学习

https://hexoimg.oss-cn-shanghai.aliyuncs.com/blog/24/11/image_3493daa39d4c7cee5bb2850022f47763.png

参考文档:Armv8-A Instruction Set Architecture.pdf

一、前言

Armv8-A 中的指令集 Armv8-A 支持三种指令集:A32、T32 和 A64。

在AArch64执行状态下执行时使用A64指令集。它是一个固定长度的32位指令集。名称中的 “64”指的是 AArch64 执行状态对该指令的使用。它不是指内存中指令的大小。

A32 和 T32 指令集也分别称为“ARM”和“Thumb”。这些指令集在 AArch32 执行状态下执行时使用。在本指南中,我们不介绍 A32 和 T32 指令集。

每个版本的 Arm 架构都有自己的 Arm 架构参考手册(Arm ARM),可以在 Arm 开发者网站上找到。每个Arm ARM都提供了每条指令的详细说明,包括:

  • 编码——指令在内存中的表示。
  • 参数 - 指令的输入。
  • 伪代码 - 指令的作用,以 Arm 伪代码语言表示。
  • 限制 - 当指令不能使用时,或者它可以触发的异常。

A64 的指令描述也以 XML 和 HTML 形式提供。如果您需要经常参考说明,则 XML 和 HTML 格式

非常有用。 XML 和 HTML 格式可以在 Arm 开发者网站上找到。

1.1 指令概况

指令基本格式

<Opcode>{<Cond>}<S> <Rd>, <Rn> {,<Opcode2>}

其中尖括号是必须的,花括号是可选的

  • A32Rd => {R0R14}
  • A64Rd =>Xt => {X0X30}

1.2 指令分类

类型 Note
跳转指令 条件跳转、无条件跳转(#immregister)指令
异常产生指令 系统调用类指令(SVCHVCSMC
系统寄存器指令 读写系统寄存器,如 :MRSMSR指令 可操作PSTATE的位段寄存器
数据处理指令 包括各种算数运算、逻辑运算、位操作、移位*(shift)*指令
load/store内存访问指令 load/store {批量寄存器、单个寄存器、一对寄存器、非-暂存、非特权、独占}以及load-Acquirestore-Release指令 (A64没有LDM/STM指令)
协处理指令 A64没有协处理器指令

1.3 指令助记符

整型
W/R 32bit整数
X 64bit整数
加载*/存储、符号-0*扩展
B 无符号8bit字节
SB 带符号8bit字节
H 无符号16bit半字
SH 带符号16bit半字
W 无符号32bit
SW 带符号32bit
P Pair(一对)
寄存器宽度改变
H 高位(dst gets top half
N 有限位(dst < src
L Longdst > src
W Wide (dst==src1,src1>src2)

1.4 指令条件码

编码 助记符 描述 标记
0000 EQ 运算结果相等为1 Z==1
0001 NE 运算结果不等为0 Z==0
0010 HS/CS 无符号高或者相同进位,发生进位为1 C==1
0011 LO/CC 无符号低清零,发生借位为0 C==0
0100 MI 负数为1 N==1
0101 PL 非负数0 N==0
0110 VS 有符号溢出为1 V==1
0111 VC 没用溢出为0 V==0
1000 HI 无符号*>* C==1 && Z==0
1001 LS 无符号*<=* !(C==1 && Z==0)
1010 GE 带符号*>=* N==V
1011 LT 带符号*<* N!=V
1100 GT 带符号*>* Z==0 && N==V
1101 LE 带符号*<=* !( Z==0 && N==V)
1110 AL **无条件执行 Any
1111 NV

二、跳转指令

通常,处理器按程序顺序执行指令。这意味着处理器按照指令在内存中设置的顺序执行指令。更改此

顺序的一种方法是使用分支指令。分支指令改变程序流程并用于循环、决策和函数调用。

A64指令集还有一些条件分支指令。这些指令会根据先前指令的结果更改其执行方式。

2.1 无条件跳转指令

1
2
3
4
5
6
7
8
9
# 无条件跳转label指令
B <label>
BL <label>

# 无条件跳转register指令
BR Xn
BLR Xn
RET {Xn}

2.2 条件跳转指令

1
2
3
4
5
6
B.<cond> <label>
CBZ <Xn> <label>
CBNZ <Xn> <label>
TBZ <Xn>, #<imm>, <label>
TBNZ <Xn>, #<imm>, <label>

2.3 举例说明

第一个例子:如果a等于5,则b等于5

1
2
3
if (a == 5)
b = 5;

使用汇编编写,如下所示

1
2
3
4
5
6
7
CMP W0, #5
B.NE skip
MOV W8, #5

skip:
....

第二个例子:当a不等于0时,b+c赋值给b,a-1赋值给a

1
2
3
4
5
6
while(a!=0)
{
b = b+c;
a = a-1;
}

使用汇编

1
2
3
4
5
6
7
8
9
loop:
CBZ W8, skip # 判断W8是否为0,也就是a是否为0,如果为0跳出循环
ADD W9, W9, W10 # 否则W9/W10寄存器中的值相加赋值给W9,也就是b+c赋值给b
SUB W8, W8, #1 # W8-1赋值给W8
B loop # 跳转循环

skip:
...

三、PC相对寻址

读取PC的方法:PC相对地址指令(ADR, ADRP),以及branch-and-link指令(BL和BLR)会将PC地址存储在LR寄存器

修改PC的方法:使用显示控制流指令:条件分支、无条件分支、异常产生和异常返回指令

相对于A32的操作PC的MOV指令,A64已经不支持

3.1 ADR指令

使用格式:ADR register exper

编译时,首先会计算出当前PC到exper的偏移量#offset_to_exper

然后会用ADD或者SUB指令,来替换这个指令;例如等效于:ADD register,PC,#offset_to_exper

register就是exper的地址

3.2 ADRP指令

使用格式:ADRP register exper

编译时,会计算出当前PC到exper的偏移量#offset_to_exper

pc的低12位清零,然后加上偏移量给register,得到的地址时含有label的4kb对齐的内存区域的base地址

3.3 实例

  1. 本代码取自warm项目开机汇编代码的head.S的部分

解释:

  • 第365行:读取init_idmap_pd_dir的地址并写入到x0寄存器
  • 第366行:读取init_idmap_pd_end的地址并写入x1寄存器
  • 第372行:读取init_pd_dir的地址并写入x0寄存器

备注:这部分的地址在vmlinux.lds.S中被定义

四、系统操作指令

4.1 cache操作指令DC/IC

分为DC/IC指令,两者的区别在于DC为操作D-cache的指令,IC为操作I-cache的指令

这部分详看Armv8-A.pdf中的第D4.4.8 A64 Cache maintenance instructions部分

这部分还没有看,到后面专门学cache后来补充这部分内容,暂时设为TODO

4.2 地址翻译指令AT

以AT S1E1R为例

就是说给一个EL1或者EL2权限的虚拟地址,执行此AT指令后,就可以在PAR_EL1寄存器中读到翻译后的地址

下面请看这段c代码

read_sys_reg_par函数其实就是用的MRS指令读取PAR_EL1寄存器

4.3 TLBI指令

TLBI指令用于使 TLBs 中的条目无效。此指令的语法为:

TLBI < type >< level >{IS|OS} {, < xt >}

其中,

< type >,哪些条目无效

All - 所有条目

VA - 匹配在 Xt 的 VA 和 ASID 的条目 [ Entry matching VA and ASID in Xt ]

VAA - 匹配在 Xt 中的 VA ,任何 ASID 的条目

ASID - 匹配在 Xt 中的 ASID 的任何条目

< level >,要操作的地址空间

E1 = EL0/1虚拟地址空间

E2 = EL2虚拟地址空间

E3 = EL3虚拟地址空间

< IS|OS >,无论一个操作是内部可共享(IS)还是外部可共享(OS)

当 IS 添加到操作时,它将广播到内部共享域中的其他核心

当 OS 添加到操作时,它将广播到外部共享域的其他核心(在Armv8.4-A中添加)

< Xt >,操作哪个地址或ASID

仅用于按地址或ASID进行的操作

关于TLB的介绍,后面在学习到TLB章节时详细描述,本节仅描述此指令的功能以及用法

以linux内核api flush_tlb_all函数为例

最终调用的指令位tlbi vmalle1is

五、异常产生和返回指令

指令 解释
SVC SVC系统调用,目标异常等级为EL1
HVC HVC系统调用,目标异常等级为EL2
SMC SMC系统调用,目标异常等级为EL3
ERET 异常返回,使用当前的SPSR_ELxELR_ELx

六、系统存储器指令

指令 解释
MRS *R <- S:通用寄存器<=*系统寄存器
MSR *S <- R:系统寄存器<=*通用寄存器

七、数据运算指令

算数运算 逻辑运算 数据传输 地址生成 位段移动 移位运算
ADDS ANDS MOV ADRP BFM ASR
SUBS EOR MOVZ ADR SBFM LSL
CMP ORR MOVK UBFM LSR
SBC MOVI BFI ROR
RSB TST BFXIL
RSC SBFIZ
CMN SBFX
MADD UBFIZ
MSUB
MUL
SMADDL
SDIV
UDIV

7.1 算术运算指令

指令 解释
ADDS 加法指令,若S存在,则更新条件位flag
ADCS 带进位的加法,若S存在,则更新条件位flag
SUBS 减法指令,若S存在,则更新条件位flag
SBC 将操作数1减去操作数2,再减去 标志位C的取反值 ,结果送到目的寄存器Xt/Wt
RSB 逆向减法,操作数2–操作数1,结果Rd
RSC 带借位的逆向减法指令,将操作数2减去操作数1,再减去 标志位C的取反值 ,结果送目标寄存器Xt/Wt
CMP 比较相等指令
CMN 比较不等指令
NEG 取负数运算,NEG X1X2 // X1 = X2按位取反*+1*(负数*=正数补码+1*)
MADD 乘加运算
MSUB 乘减运算
MUL 乘法运算
SMADDL 有符号乘加运算
SDIV 有符号除法运算
UDIV 无符号除法运算

7.2 逻辑运算指令

指令 解释
ANDS 按位与运算,如果S存在,则更新条件位标记
EOR 按位异或运算
ORR 按位或运算
TST 例如:TST W0, #0X40 //指令用来测试W0[3]是否为1,相当于:ANDS WZR,W0#0X40

不再多介绍,这部分遇到了直接查

八、load/store指令

对齐偏移 非对齐偏移 **PC-相对寻址 访问一对 非暂存 非特权 独占 AcquireRelease
LDR LDUR LDR LDP LDNP LDTR LDXR LDAR
LDRB LDURB LDRSW LDRSW STNP LDTRB LDXRB LDARB
LDRSB LDURSB STP LDTRSB LDXRH LDARH
LDRH LDURH LDTRH LDXP STLR
LDRSH LDURSH LDTRSH STXR STLRB
LDRSW LDURSW LDTRSW STXRB STLRH
STR STUR STTR STXRH LDAXR
STRB STURB STTRB STXP LDAXRB
STRH STURH STTRH LDAXRH
LDAXP
STLXR
STLXRB
STLXRH
STLXP

8.1 Load/Store (Scaled Offset)

所谓ScaledUnscaled其实就是可以见到理解为对齐和非对齐,本质就是是否乘以一个常量,因为scaled的总是可以乘以一个常量来达到对齐,而Unscaled就不需要,是多少就多少,更符合人类自然的理解

指令 解释
LDR *Memory地址addr中读取双字/字节/半字/字数据到目标寄存器Xt/Wt中带”S”表示需要符号扩展.***
LDRB
LDRSB
LDRH
LDRSH
LDRSW
STR Xn/Wn中的双字*/字节/半字数据写入到Memory地址addr*中
STRB
STRH

8.2 Load/Store (Unscaled Offset)

指令 解释
LDUR Memory地址addr中读取双字*/字节/半字/字数据到目标寄存器Xt/Wt中带”S”表示需要符号扩展.立即数偏移#simm9 = { -256 ~ +256 }的任意整数,不需要对齐规则.*
LDURB
LDURSB
LDURH
LDURSH
LDURSW
STUR Xn/Wn中的双字*/字节/半字数据写入到Memory地址addr中立即数偏移#simm9 = { -256 ~ +256 }的任意整数,不需要对齐规则.*
STURB
STURH

8.3 Load/Store PC-relative(PC相对寻址)

指令 解释
LDR Memory地址addr中读取双字*/字数据到目标寄存器Xt/Wt中带”S”表示需要符号扩展.*
LDRSW

8.4 Load/Store Pair(一对)

指令 解释
LDP Memory地址addr处读取两个双字*/字数据到目标寄存器Xt1*,Xt2带”S”表示需要符号扩展*.*
LDRSW
STP Xt1Xt2两个双字*/字数据写到Memory地址addr*中

8.5 Load/Store Non-temporal(非暂存) Pair

所谓Non-temporal就是就是用于你确定知道该地址只加载一次,不需要触发缓存,避免数据被刷新,优化性能,其它指令都默认会写Cache

指令 解释
LDNP Memory地址addr处读取两个双字*/字数据到目标寄存器Xt1*,Xt2,标注非暂存访问,不更新cache带”S”表示需要符号扩展*.*
STNP Xt1Xt2两个双字*/字数据写到Memory地址addr中,标注非暂存访问,不更新cache*

8.6 Load/Store Unprivileged(非特权)

所谓Unprivileged就是说EL0/EL1的内存有不同的权限控制,这条指令以EL0的权限存取,用于模拟EL0的行为,该指令应用于EL1和EL0之间的交互.

指令 解释
LDTR Memory地址addr中读取双字*/字节/半字/字数据到目标寄存器Xt/Wt中,当执行在EL1的时候使用EL0*的权限带”S”表示需要符号扩展
LDTRB
LDTRSB
LDTRH
LDTRSH
LDTRSW
STTR Xn/Wn中的双字*/字节/半字数据写入到Memory地址addr中,当执行在EL1的时候使用EL0*的权限
STTRB
STTRH

8.7 Load/Store Exclusive(独占)

在多核CPU下,对一个地址的访问可能引起冲突,这个指令解决了冲突,保证原子性(所谓原子操作简单理解就是不能被中断的操作),是解决多个CPU访问同一内存地址导致冲突的一种机制。

比如2个CPU同时写,其中一条的Ws就会返回失败值。通常用于锁,比如spinlock,可以参考代码:arch/arm64/include/asm/spinlock.h

指令 解释
LDXR Memory地址addr中读取双字*/字节/半字数据到目标寄存器Xt/Wt*中,标记物理地址是独占访问的
LDXRB
LDXRH
LDXP Memory地址addr中读取一对双字数据到目标寄存器Xt1Xt2中,标记物理地址是独占访问的
STXR Xn/Wn中的双字*/字节/半字数据写入到Memory地址addr*中,返回是否独占访问成功状态(Ws
STXRB
STXRH
STXP Xt1Xt2一对双字字数据写入到Memory地址addr中,返回是否独占访问成功状态

8.8 Load-Acquire/Store-Release

指令 解释
Non-exclusive(非独占)
LDAR Memory地址addr中读取一个双字*/字节/半字数据到目标寄存器Xt/Wt*中,标记物理地址为非独占访问
LDARB
LDARH
STLR 把一个双字*/字节/半字数据Xt/Wt写到Memory地址addr*中,返回是否独占访问成功状态
STLRB
STLRH
Exclusive(独占)
LDAXR Memory地址addr中读取一个双字*/字节/半字数据到目标寄存器Xt/Wt中,标记物理地址为独占访问LDAXPPair* 访问
LDAXRB
LDAXRH
LDAXP
STLXR 把一个双字*/字节/半字数据Xt/Wt写到Memory地址addr中,返回是否独占访问成功状态STLXPPair* 访问
STLXRB
STLXRH
STLXP

九、内存屏蔽指令

指令 翻译 解释
DMB 数据内存屏障指令 保证该指令前的所有内存访问结束,而该指令之后引起的内存访问只能在该指令执行结束后开始,其它数据处理指令等可以越过DMB屏障乱序执行
DSB 数据同步屏障指令 DSBDMB管得更宽,DSB屏障之后的所有得指令不可越过屏障乱序执行
ISB 指令同步屏障指令 ISBDSB管的更宽,ISB屏障之前的指令保证执行完,屏障之后的指令直接flush掉再重新从Memroy中取指

在第4.2 地址翻译指令AT的案例中也使用了dsb的内存屏蔽指令