A64 指令集提供了许多不同种类的分支指令。对于简单的相对分支,即那些从当前地址偏移的分支,使用 B 指令。无条件简单相对分支可以从当前程序计数器位置向后或向前分支最多 128MB。 有条件的简单相对分支,其中条件代码附加到 B,具有 ±1MB 的较小范围。

调用子程序时,需要将返回地址存储在链接寄存器(X30)中,使用 BL 指令。这没有条件版本。BL 的行为类似于 B 指令,具有将返回地址存储在寄存器 X30 中的附加作用,返回地址是 BL 之后指令的地址。

除了这些 PC 相关指令外,A64 指令集还包括两个绝对分支。 BR Xn 指令对 Xn 中的地址执行绝对分支,而 BLR Xn 具有相同的效果,但还将返回地址存储在 X30(链接寄存器)中。 RET 指令的行为类似于 BR Xn,但它向分支预测逻辑提示它是一个函数返回。RET 默认分支到 X30 中的地址,但可以指定其他寄存器。

A64 指令集包括一些特殊的条件分支。在某些情况下,这些允许提高代码密度,因为不需要显式比较。

CBZ Rt, label // 如果为零则比较并分支
CBNZ Rt, label // 如果不为零则比较并分支

这些指令将 32 位或 64 位源寄存器与零进行比较,然后有条件地执行分支。分支偏移量的范围为 ± 1MB。这些指令不读取或写入条件代码标志(NZCV)。

有两个类似的测试和分支指令

TBZ Rt, bit, label // 如果 Rt<bit> 为零,则测试并分支
TBNZ Rt, bit, label // 如果 Rt<bit> 不为零,则测试并分支

这些指令在立即和条件分支指定的位位置测试源寄存器中的位,具体取决于该位是设置还是清除。分支偏移量的范围为 ±32kB。与 CBZ/CBNZ 一样,这些指令不读取或写入条件代码标志(NZCV)。

一、分支指令

1.1 B

分支指令导致无条件分支到 PC 相对偏移处的标签,并提示这不是子例程调用或返回。

B <label>

<label> 是要无条件跳转到的程序标签。 它与该指令地址的偏移量在 +/-128MB 范围内,编码为“imm26”乘以 4。

下面是使用 B 指令的例子。

    long long int x = 0;long long int y = 0;long long int z = 0;LOGD("x=%llx y=%llx z=%llx", x, y, z);asm volatile("MOV %x[x], #1\n""B skipy\n""MOV %x[y], #2\n""skipy:\n""MOV %x[z], #3\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx z=%llx", x, y, z);

由于 B skipy 跳过了 MOV %x[y], #2 这条指令,因此 y 的值还是初始值。

运行结果如下:

2023-05-05 07:59:28.672 12642-12704/com.demo.myapplication D/NativeCore: x=0 y=0 z=0
2023-05-05 07:59:28.672 12642-12704/com.demo.myapplication D/NativeCore: -----------------------------
2023-05-05 07:59:28.672 12642-12704/com.demo.myapplication D/NativeCore: x=1 y=0 z=3

1.2 B.cond

有条件地分支到 PC 相对偏移处的标签,并提示这不是子例程调用或返回。

B.<cond> <label>

<cond> 是标准条件之一,以标准方式编码在“cond”字段中。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-1MB 范围内,编码为“imm19”乘以 4。

下面是使用 B.cond 指令的例子。

    long long int x = 0;long long int y = 0;long long int z = 0;LOGD("x=%llx y=%llx z=%llx", x, y, z);asm volatile("MOV X0, #2\n""SUBS X0, X0, #1\n""B.EQ skipy\n""MOV %x[x], #1\n""SUBS X0, X0, #1\n""B.EQ skipy\n""MOV %x[y], #2\n""skipy:\n""MOV %x[z], #3\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx z=%llx", x, y, z);

由于第二条 B.EQ skipy 跳过了 MOV %x[y], #2 这条指令,因此 y 的值还是初始值。第一条 B.EQ skipy 指令因为 第一条 SUBS X0, X0, #1 指令并没有设置 Z 标志位,所以没有跳转,否则 x 的值最终也不会改变。

运行结果如下:

2023-05-05 08:08:27.579 16848-16909/com.demo.myapplication D/NativeCore: x=0 y=0 z=0
2023-05-05 08:08:27.579 16848-16909/com.demo.myapplication D/NativeCore: -----------------------------
2023-05-05 08:08:27.579 16848-16909/com.demo.myapplication D/NativeCore: x=1 y=0 z=3

1.3 BL

BL(Branch with Link)指令分支到 PC 相对偏移,将寄存器 X30 设置为 PC + 4。它提供了这是一个子例程调用的提示。

BL <label>

<label> 是要无条件跳转到的程序标签。它与该指令地址的偏移量在 +/-128MB 范围内,编码为“imm26”乘以 4。

下面是使用 BL 指令的例子。

    long long int x = 0;long long int y = 0;long long int z = 0;LOGD("x=%llx y=%llx z=%llx", x, y, z);asm volatile("BL mod_y\n""MOV %x[x], #1\n""B mod_z\n""MOV %x[x], #4\n""mod_y:\n""MOV %x[y], #2\n""BR LR\n""mod_z:\n""MOV %x[z], #3\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx z=%llx", x, y, z);

运行结果如下:

2023-05-05 09:13:40.038 15433-15580/com.demo.myapplication D/NativeCore: x=0 y=0 z=0
2023-05-05 09:13:40.038 15433-15580/com.demo.myapplication D/NativeCore: -----------------------------
2023-05-05 09:13:40.038 15433-15580/com.demo.myapplication D/NativeCore: x=1 y=2 z=3

1.4 BR

BR (Branch to Register)指令无条件地分支到寄存器中的地址,并提示这不是子程序返回。

BR <Xn>

<Xn> 是保存要分支到的地址的通用寄存器的 64 位名称,在“Rn”字段中编码。

BR 指令的例子参考 BL 指令的例子即可。

1.5 BLR

BLR(Branch with Link to Register)指令在寄存器中的地址调用子程序,将寄存器 X30 设置为 PC+4。

BLR <Xn>

<Xn> 是保存要分支到的地址的通用寄存器的 64 位名称,在“Rn”字段中编码。

下面是使用 BLR 指令的例子。

    long long int x = 0;long long int y = 0;long long int z = 0;LOGD("x=%llx y=%llx z=%llx", x, y, z);asm volatile("BL mod_y\n""MOV %x[x], #1\n""BR LR\n""MOV %x[x], #4\n""mod_y:\n""MOV %x[y], #2\n""BLR LR\n""MOV %x[y], #5\n""mod_z:\n""MOV %x[z], #3\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx z=%llx", x, y, z);

BLR 指令执行会设置 LR 寄存器的值为 PC + 4,BR 指令则只分支!

运行结果如下:

2023-05-07 15:58:22.939 4243-4243/com.example.myapplication D/native-armv8a: x=0 y=0 z=0
2023-05-07 15:58:22.939 4243-4243/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 15:58:22.939 4243-4243/com.example.myapplication D/native-armv8a: x=1 y=5 z=3

1.6 RET

无条件地从子程序分支返回到寄存器中的地址,并提示这是子程序返回。

RET {<Xn>}

<Xn> 是保存要分支到的地址的通用寄存器的 64 位名称,在“Rn”字段中编码。 如果不存在则默认为 X30。

下面是使用 RET 指令的例子。将上面例子中 BR LR 改为 RET,x、y、z 最终的值和上面的例子保持一致。

    long long int x = 0;long long int y = 0;long long int z = 0;LOGD("x=%llx y=%llx z=%llx", x, y, z);asm volatile("BL mod_y\n""MOV %x[x], #1\n""RET\n""MOV %x[x], #4\n""mod_y:\n""MOV %x[y], #2\n""BLR LR\n""MOV %x[y], #5\n""mod_z:\n""MOV %x[z], #3\n":[x] "+r"(x),[y] "+r"(y),[z] "+r"(z):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx z=%llx", x, y, z);

运行结果如下:

2023-05-07 16:21:41.276 19720-19720/com.example.myapplication D/native-armv8a: x=0 y=0 z=0
2023-05-07 16:21:41.276 19720-19720/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 16:21:41.276 19720-19720/com.example.myapplication D/native-armv8a: x=1 y=5 z=3

二、条件分支指令

2.1 CBZ

CBZ(Compare and Branch on Zero)指令将寄存器中的值与零进行比较,如果比较相等,则有条件地分支到 PC 相对偏移量处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

32-bit (sf == 0)

CBZ <Wt>, <label>

64-bit (sf == 1)

CBZ <Xt>, <label>

<Wt> 是要测试的通用寄存器的 32 位名称,编码在“Rt”字段中。

<Xt> 是要测试的通用寄存器的 64 位名称,编码在“Rt”字段中。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-1MB 范围内,编码为“imm19”乘以 4。

下面是使用 CBZ 指令的例子。

    long long int x = 0;long long int y = 0;LOGD("x=%llx y=%llx", x, y);asm volatile("MOV X0, #1\n""CBZ X0, mod_y\n""MOV %x[x], #1\n""SUB X0, X0, #1\n""CBZ X0, mod_y\n""MOV %x[x], #3\n""mod_y:\n""MOV %x[y], #2\n":[x] "+r"(x),[y] "+r"(y):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx", x, y);

第一次执行 CBZ X0, mod_y ,X0 的值不为 0,因此会继续向下执行到 MOV %x[x], #1,接着 SUB X0, X0, #1 对 X0 寄存器减一操作,X0 的值已经为 0,第二次执行 CBZ X0, mod_y 已经满足分支到 mod_y 标签处,所以直接跳到 mod_y 处执行(跳过了 MOV %x[x], #3)。也就是最终 x 的值为 1,y 的值为 2。

运行结果如下:

2023-05-07 16:29:49.401 22423-22423/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 16:29:49.401 22423-22423/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 16:29:49.401 22423-22423/com.example.myapplication D/native-armv8a: x=1 y=2

2.2 CBNZ

CBNZ(Compare and Branch on Nonzero)指令将寄存器中的值与零进行比较,如果比较不相等,则有条件地分支到 PC 相对偏移处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

32-bit (sf == 0)

CBNZ <Wt>, <label>

64-bit (sf == 1)

CBNZ <Xt>, <label>

<Wt> 是要测试的通用寄存器的 32 位名称,编码在“Rt”字段中。

<Xt> 是要测试的通用寄存器的 64 位名称,编码在“Rt”字段中。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-1MB 范围内,编码为“imm19”乘以 4。

下面是使用 CBNZ 指令的例子。修改 CBZ 指令例子中的第一条 CBZ 指令为 CBNZ 即可。

    long long int x = 0;long long int y = 0;LOGD("x=%llx y=%llx", x, y);asm volatile("MOV X0, #1\n""CBNZ X0, mod_y\n""MOV %x[x], #1\n""SUB X0, X0, #1\n""CBZ X0, mod_y\n""MOV %x[x], #3\n""mod_y:\n""MOV %x[y], #2\n":[x] "+r"(x),[y] "+r"(y):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx", x, y);

第一次执行 CBNZ X0, mod_y ,X0 的值不为 0,因此直接分支到 mod_y 标签处,所以直接跳到 mod_y 处执行(跳过了 中间的其他指令)。也就是最终 x 的值还为 0,但 y 的值已经被修改为 2。

运行结果如下:

2023-05-07 16:36:26.884 31211-31211/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 16:36:26.884 31211-31211/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 16:36:26.884 31211-31211/com.example.myapplication D/native-armv8a: x=0 y=2

2.3 TBZ

TBZ(Test bit and Branch if Zero)指令将测试位的值与零进行比较,如果比较相等,则有条件地分支到 PC 相对偏移处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

TBZ <R><t>, #<imm>, <label>

<R> 是宽度说明符,在“b5”字段中编码。它可以具有以下值:

R b5
W 0
X 1

在汇编程序源代码中,始终允许使用“X”说明符,但仅当位数小于 32 时才允许使用“W”说明符。

<t> 为待测通用寄存器的编号 [0-30] 或名称 ZR(31),编码在“Rt”字段中。

<imm> 为要测试的位数,取值范围为 0~63,编码为“b5:b40”。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-32KB 范围内,被编码为“imm14”乘以 4。

下面是使用 TBZ 指令的例子。

    long long int x = 0;long long int y = 0;LOGD("x=%llx y=%llx", x, y);asm volatile("MOV X0, #0b100\n""TBZ X0, 2, mod_y\n""MOV %x[x], #1\n""TBZ X0, 1, mod_y\n""MOV %x[x], #3\n""mod_y:\n""MOV %x[y], #2\n":[x] "+r"(x),[y] "+r"(y):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx", x, y);

执行 MOV X0, #0b100 后,X0 的值为 0b100(也就是从低到高第三位不为 0,从 0 开始计数的话即位的 index 为 2),TBZ X0, 2, mod_y 测试并比较位 index 为 2 处 X0 对应的位,此位为 1,所以不满足 TBZ 分支到 mod_y 处的情况,这会继续执行 MOV %x[x], #1 ,第二次执行 TBZ 指令 TBZ X0, 1, mod\_y,由于满足分支条件,直接跳到 mod_y 处执行(跳过了 MOV %x[x], #3),最终 x 的值为 1,但 y 的值已经被修改为 2。

运行结果如下:

2023-05-07 17:16:43.303 13323-13323/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 17:16:43.303 13323-13323/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 17:16:43.303 13323-13323/com.example.myapplication D/native-armv8a: x=1 y=2

2.4 TBNZ

TBNZ(Test bit and Branch if Nonzero)指令将通用寄存器中的位值与零进行比较,如果比较不相等,则有条件地分支到 PC 相对偏移量处的标签。它提供了这不是子例程调用或返回的提示。该指令不影响条件标志。

TBNZ <R><t>, #<imm>, <label>

<R> 是宽度说明符,在“b5”字段中编码。它可以具有以下值:

R b5
W 0
X 1

在汇编程序源代码中,始终允许使用“X”说明符,但仅当位数小于 32 时才允许使用“W”说明符。

<t> 为待测通用寄存器的编号 [0-30] 或名称 ZR(31),编码在“Rt”字段中。

<imm> 为要测试的位数,取值范围为 0~63,编码为“b5:b40”。

<label> 是有条件跳转到的程序标签。它与该指令地址的偏移量在 +/-32KB 范围内,被编码为“imm14”乘以 4。

下面是使用 TBNZ 指令的例子。

    long long int x = 0;long long int y = 0;LOGD("x=%llx y=%llx", x, y);asm volatile("MOV X0, #0b100\n""TBNZ X0, 2, mod_y\n""MOV %x[x], #1\n""mod_y:\n""MOV %x[y], #2\n":[x] "+r"(x),[y] "+r"(y):: "cc", "memory");LOGD("-----------------------------");LOGD("x=%llx y=%llx", x, y);

执行 MOV X0, #0b100 后,X0 的值为 0b100(也就是从低到高第三位不为 0,从 0 开始计数的话即位的 index 为 2),TBNZ X0, 2, mod_y 测试并比较位 index 为 2 处 X0 对应的位,此位为 1,所以满足 TBNZ 分支到 mod_y 处的情况,这会跳过 MOV %x[x], #1 执行,最终 x 的值还为 0,但 y 的值已经被修改为 2。

运行结果如下:

2023-05-07 17:07:33.926 10241-10241/com.example.myapplication D/native-armv8a: x=0 y=0
2023-05-07 17:07:33.926 10241-10241/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-07 17:07:33.926 10241-10241/com.example.myapplication D/native-armv8a: x=0 y=2

参考资料

1.《ARMv8-A-Programmer-Guide》
2.《Arm® A64 Instruction Set Architecture Armv8, for Armv8-A architecture profile》

【ARMv8 编程】A64 流控制指令相关推荐

  1. 【ARMv8 编程】A64 数据处理指令——算术指令

    许多在应用程序级别编写代码的程序员不需要用汇编语言编写代码.然而,汇编代码在需要高度优化的代码的情况下很有用.在编写编译器时,或者需要使用 C 语言中无法直接提供的底层特性时,都会出现这种情况.部分引 ...

  2. 【ARMv8 SIMD和浮点指令编程】编程基础

    ARM 高级 SIMD 架构.相关的实现和支持软件通常被称为 NEON 技术.AArch32(相当于 ARMv7 的 NEON 指令)和 AArch64 都有 NEON 指令集.两者都可以显著加速在大 ...

  3. 【ARMv8 编程】A64 数据处理指令——位域字节操作指令

    有些指令将字节.半字或字扩展到寄存器大小,可以是 X 或 W.这些指令存在于有符号(SXTB.SXTH.SXTW)和无符号(UXTB.UXTH)变体中,并且是适当的位域操作指令. 这些指令的有符号和无 ...

  4. 【ARMv8 编程】A64 数据处理指令——逻辑移位指令

    逻辑指令包括与.或等指令,移位指令则分为逻辑移位和算术移位指令,下面则详细展开学习. 指令类型 指令 逻辑 AND.BIC.ORR.ORN.EOR.EON 移位 ASR.LSL.LSR.ROR 逻辑运 ...

  5. 【ARMv8 编程】A64 数据处理指令——移动比较指令

    移动指令主要为 MOV 以及它的各种"变体",而比较指令主要用来进行比较并更新条件标志,用来实现条件判断等. 指令类型 指令 移动 MOV.MVN.MOVK.MOVZ.MOVN 比 ...

  6. 【ARMv8 编程】A64 内存访问指令——内存存储指令

    在内存加载一节中实际上已经使用了内存存储指令了,内存存储指令将寄存器的值存储到内存中. 同样,Store 指令的一般形式如下: STR Rn, <addr> 还有 unscaled-off ...

  7. 【ARMv8 编程】A64 内存访问指令——内存加载指令

    与所有先前的 ARM 处理器一样,ARMv8 架构是一种加载/存储架构.这意味着没有数据处理指令直接对内存中的数据进行操作.数据必须首先被加载到寄存器中,修改,然后存储到内存中.该程序必须指定地址.要 ...

  8. 【ARMv8 SIMD和浮点指令编程】Libyuv I420 转 ARGB 流程分析

    Libyuv 可以说是做图形图像相关从业者绕不开的一个常用库,它使用了单指令多数据流提升性能.以 ARM 处理为主线,通过 I420 转 ARGB 流程来分析它是如何流转的. Libyuv 是一个开源 ...

  9. 【ARMv8 编程】寄存器

    ARMv8 用于描述整体架构,包括 32 位执行和 64 位执行.它使用 64 位位宽寄存器,同时保持向后兼容 v7. 现在来看看 ARMv8 都有哪些改进: 大的物理地址 这使处理器能够访问超过 4 ...

最新文章

  1. AbutionGraph:构建以知识图谱为核心的下一代数据中台
  2. SAP HUM 内向交货单凭证流和Relationship Browser
  3. form表单会跨域_前端跨域So Easy
  4. [踏得网]HTML5在线教程阅读进度记录
  5. Redis字符串操作
  6. IIS Form 认证 保护HTML页面
  7. java mapreduce api_Hadoop实战-初级部分 之 Hadoop MapReduce JAVA API
  8. Swift学习笔记 In-out形参
  9. 虚拟机桥接模式-CentOS下配置静态IP
  10. Serializable接口序列化与反序列化
  11. Android PackageManager 详解
  12. JS编写 简易网页音乐播放器
  13. 统计检验的基本原理(异常值检验)
  14. java最小因子_一个整数的所有最小因子
  15. 文献综述 android,《基于android的手机天气预报系统》的文献综述-文献综述
  16. kafka设置起止时间消费消息
  17. python2.7下安装PIL库
  18. 如何建设企业入侵防御体系
  19. 万能DOS启动盘制作全攻略!(软盘+光盘+U盘+硬盘+NTFS+……)
  20. Win32 API概述

热门文章

  1. 定量蛋白组学分析-蛋白质非标记定量技术lable free
  2. eCargo国际货代系统之公路运输管理系统
  3. html div虚线背景,关于CSS(DIV属性,背景,连接,边框,虚线,实线,布局与命名)...
  4. aelf Enterprise 1.0.0 Preview 2 版正式发布!
  5. aelf性能测评 | 以太坊layer 2赛道中的佼佼者,aelf Enterprise稳定TPS达3.6万
  6. 微信域名检测 腾讯微信域名检测的机制原理以及实现方式
  7. 泛社交化游戏中的平衡问题
  8. 苹果“土豪金”又多增伙伴:三星Galaxy S4
  9. linux内核看门狗关闭方法,linux内核中断之看门狗
  10. Redis哨兵配置及部署