ARM64 指令 --- CCMP/CSEL

文章目录

  • 一、CCMP
    • [1.1 CCMP (immediate)](#1.1 CCMP (immediate))
    • [1.2 CCMP (register)](#1.2 CCMP (register))
  • 二、CSEL
  • 三、demo
    • [3.1 demo1](#3.1 demo1)
    • [3.2 demo2](#3.2 demo2)
    • [3.3 demo3](#3.3 demo3)

一、CCMP

1.1 CCMP (immediate)

条件比较(CCMP)立即数指令是ARMv8架构中的一种高级条件执行指令。它根据前一个条件的结果来决定是否执行当前的比较操作,用于有条件地更新处理器的状态标志位(NZCV)。

Encoding for the 64-bit variant:

c 复制代码
CCMP <Xn>, #<imm>, #<nzcv>, <cond>

<Xn>:64 位(Xn)通用寄存器

#imm:5位无符号(正)立即数,取值范围:0-31(2⁵-1)

#nzcv:一个 4 位立即数,取值范围:0-15(2⁴-1),直接指定 N、Z、C、V 的值(当条件不满足时使用)

<cond>:ARM 条件码(如 EQ、NE、LT、GE 等)

执行流程:

c 复制代码
如果(条件码)为真:
    执行 Rn - 立即数 的比较
    将结果的条件标志(NZCV)存入 PSTATE
否则:
    直接将指定的 NZCV值 存入 PSTATE

NZCV相关知识请参考:ARM64 --- NZCV标志位

举例说明:

c 复制代码
CCMP W0, #10, #0b0001, NE

如果 NE 条件成立(即前一次操作结果不为零):

比较 W0 和 10,根据 W0 - 10 的结果设置 NZCV

如果 NE 不成立(即前一次结果为零):

直接设置标志位为:N=0, Z=0, C=0, V=1(因为 0b0001 表示 V=1)

备注:

条件码依赖:CCMP依赖前一条指令设置的标志位

立即数范围:立即数通常有范围限制(如0-31)

标志位设置:NZCV参数直接设置所有4个标志位

无寄存器修改:只影响标志位,不修改任何通用寄存器

1.2 CCMP (register)

条件比较(CCMP)寄存器指令是 ARMv8 架构中的另一种条件比较指令,它与立即数版本类似,但比较的是两个寄存器而非寄存器与立即数。

Encoding for the 64-bit variant:

c 复制代码
CCMP <Xn>, <Xm>, #<nzcv>, <cond>

二、CSEL

请参考:ARM64 指令 --- TST/CSEL

三、demo

3.1 demo1

我们要实现的 C 代码是:

c 复制代码
if (a != 0 && b != 0)
    y = 1;
else
    y = 0;

b 只有在 a != 0 时才参与判断

逻辑拆解:

c 复制代码
if (a != 0 && b != 0)

等价于:

c 复制代码
if (a != 0) {
    if (b != 0)
        true;
    else
        false;
} else {
    false;
}

汇编代码:

c 复制代码
.section .text
.global _start

_start:
    // -------------------------------
    // 输入
    // -------------------------------
    mov     x0, #1        // a = 1  ← 改 0 / 1 测试
    mov     x1, #1        // b = 1  ← 改 0 / 1 测试

    // -------------------------------
    // if (a != 0 && b != 0)
    // -------------------------------

    cmp     x0, #0        // a ? 0   → 设置 Z
    ccmp    x1, #0, #0b0100, ne
    //        ↑      ↑
    //        |      |
    //   if (a!=0)   else: 强制 Z=1(false)

    // -------------------------------
    // 用最终 NZCV 选结果
    // -------------------------------
    mov     x2, #1        // true
    mov     x3, #0        // false
    csel    x4, x2, x3, ne

    // 保存 NZCV
    mrs     x5, nzcv
    nop

    // exit
    mov     x8, #93
    mov     x0, #0
    svc     #0
c 复制代码
$ as -o ccmp_csel_and_demo.o ccmp_csel_and_demo.s
$ ld -o ccmp_csel_and_demo ccmp_csel_and_demo.o
$ gdb ./ccmp_csel_and_demo
......
(gdb) set disassemble-next-line on
(gdb) starti
Starting program: ccmp_csel_and_demo
c 复制代码
0x0000000000400078 in _start ()
=> 0x0000000000400078 <_start+0>:       20 00 80 d2     mov     x0, #0x1                        // #1
(gdb) si
0x000000000040007c in _start ()
=> 0x000000000040007c <_start+4>:       21 00 80 d2     mov     x1, #0x1                        // #1
(gdb) si
0x0000000000400080 in _start ()
=> 0x0000000000400080 <_start+8>:       1f 00 00 f1     cmp     x0, #0x0
(gdb) si
0x0000000000400084 in _start ()
=> 0x0000000000400084 <_start+12>:      24 18 40 fa     ccmp    x1, #0x0, #0x4, ne  // ne = any
(gdb) si
0x0000000000400088 in _start ()
=> 0x0000000000400088 <_start+16>:      22 00 80 d2     mov     x2, #0x1                        // #1
(gdb) si
0x000000000040008c in _start ()
=> 0x000000000040008c <_start+20>:      03 00 80 d2     mov     x3, #0x0                        // #0
(gdb) si
0x0000000000400090 in _start ()
=> 0x0000000000400090 <_start+24>:      44 10 83 9a     csel    x4, x2, x3, ne  // ne = any
(gdb) si
0x0000000000400094 in _start ()
=> 0x0000000000400094 <_start+28>:      05 42 3b d5     mrs     x5, nzcv
(gdb) si
0x0000000000400098 in _start ()
=> 0x0000000000400098 <_start+32>:      1f 20 03 d5     nop
(gdb) info registers x4 x5
x4             0x1                 1
x5             0x20000000          536870912

(1)

c 复制代码
cmp x0, #0

a != 0 → 结果不为零 → Z = 0

(2)

c 复制代码
ccmp x1, #0, #0b0100, ne

因为a != 0,所以进行 x1, #0 的比较,

b != 0 → 结果不为零 → Z = 0

比较其实就是相当于减法,因此:

c 复制代码
N = 0, Z = 0, C = 1, V = 0

因此 nzcv = 0b0010,所以 x5 = 0x20000000

(3)

c 复制代码
csel x4, x2, x3, ne

b != 0 → 结果不为零 → Z = 0

所以 x4 = x2 = 1

(4)四种组合的结果

a b a&&b Z x4
0 0 0 1 0
0 1 0 1 0
1 0 0 1 0
1 1 1 0 1
c 复制代码
ccmp x1, #0, #0b0100, ne

当 a == 0 时:

b 不会参与逻辑结果

NZCV 被强制设置成 Z=1

后续逻辑只看到 "false"

因此 x4 = x3 = 0.

3.2 demo2

接下来我们来把 if (a && b || c) 用 CCMP + CSEL 做成 完全无分支 的版本。

C语言代码:

c 复制代码
if (a && b || c)
    y = 1;
else
    y = 0;

运算符优先级是:

c 复制代码
(a && b) || c

并且 规则 是:

c 复制代码
若 a == 0 → a && b 为假 → 看 c

若 a != 0 && b == 0 → (a && b) 为假 → 看 c

若 a != 0 && b != 0 → (a && b) 为真 → 不看 c

若 (a && b) 为假 且 c == 0 → 假

我们分两层:

c 复制代码
先算 (a && b)

如果 (a && b) 为假,再条件性地比较 c

用 最终 NZCV → CSEL

全程 无跳转
c 复制代码
0x0000000000400078 in _start ()
=> 0x0000000000400078 <_start+0>:       20 00 80 d2     mov     x0, #0x1                        // #1
(gdb) si
0x000000000040007c in _start ()
=> 0x000000000040007c <_start+4>:       01 00 80 d2     mov     x1, #0x0                        // #0
(gdb) si
0x0000000000400080 in _start ()
=> 0x0000000000400080 <_start+8>:       22 00 80 d2     mov     x2, #0x1                        // #1
(gdb) si
0x0000000000400084 in _start ()
=> 0x0000000000400084 <_start+12>:      1f 00 00 f1     cmp     x0, #0x0
(gdb) si
0x0000000000400088 in _start ()
=> 0x0000000000400088 <_start+16>:      24 18 40 fa     ccmp    x1, #0x0, #0x4, ne  // ne = any
(gdb) si
0x000000000040008c in _start ()
=> 0x000000000040008c <_start+20>:      40 08 40 fa     ccmp    x2, #0x0, #0x0, eq  // eq = none
(gdb) si
0x0000000000400090 in _start ()
=> 0x0000000000400090 <_start+24>:      23 00 80 d2     mov     x3, #0x1                        // #1
(gdb) si
0x0000000000400094 in _start ()
=> 0x0000000000400094 <_start+28>:      04 00 80 d2     mov     x4, #0x0                        // #0
(gdb) si
0x0000000000400098 in _start ()
=> 0x0000000000400098 <_start+32>:      65 10 84 9a     csel    x5, x3, x4, ne  // ne = any
(gdb) si
0x000000000040009c in _start ()
=> 0x000000000040009c <_start+36>:      06 42 3b d5     mrs     x6, nzcv
(gdb) si
0x00000000004000a0 in _start ()
=> 0x00000000004000a0 <_start+40>:      1f 20 03 d5     nop
(gdb) info registers x5 x6
x5             0x1                 1
x6             0x20000000          536870912

(1)

c 复制代码
cmp x0, #0

x0 = 1 ,不等于0,即ne

(2)

c 复制代码
ccmp    x1, #0, #0b0100, ne

上面的结果是不等于0,即 ne ,进行x1, #0的比较,

x1 = 0,等于0

nzcv = 0b0110

(3)

c 复制代码
ccmp    x2, #0, #0b0000, eq

上面的结果等于0,即eq,进行x2, #0的比较

x2 = 1 ,不等于1,即ne

nzcv = 0b0010

(4)

c 复制代码
csel    x5, x3, x4, ne

上面的结果是不等于0,即 ne,因此

x5 = x3 = 1

四种典型组合:

a b c 结果 Z x5
0 0 0 0 1 0
0 1 1 1 0 1
1 0 1 1 0 1
1 1 0 1 0 1

3.3 demo3

C 语义:

c 复制代码
(a < b && c) || d

(1)先判断 a < b

若 false → (a < b && c) 为 false → 直接看 d

(2)若 a < b 为 true → 判断 c

若 c == 0 → (a < b && c) 为 false → 看 d

若 c != 0 → (a < b && c) 为 true → 不看 d

(3)最后根据 (a < b && c) || d 得结果

汇编:

我们用三步:

(1)cmp a, b

→ 得到 a < b

(2)ccmp c, #0, FALSE, lt

→ 完成 (a < b && c)

(3)ccmp d, #0, TRUE, eq

→ 完成 || d

(4)csel 消费最终 NZCV

c 复制代码
.section .text
.global _start

_start:
    // --------------------------------
    // 输入(随便改测试)
    // --------------------------------
    mov     x0, #3        // a
    mov     x1, #5        // b
    mov     x2, #1        // c
    mov     x3, #0        // d

    // --------------------------------
    // if ((a < b && c) || d)
    // --------------------------------

    // --- 1) a < b ---
    cmp     x0, x1        // LT if a < b

    // --- 2) (a < b && c) ---
    ccmp    x2, #0, #0b0100, lt
    // if (a < b):  cmp c, 0
    // else:        Z = 1  (false)

    // --- 3) (a < b && c) || d ---
    ccmp    x3, #0, #0b0000, eq
    // if previous == false (Z=1): cmp d, 0
    // else:                       Z = 0 (true)

    // --------------------------------
    // 结果选择
    // --------------------------------
    mov     x4, #1        // true
    mov     x5, #0        // false
    csel    x6, x4, x5, ne

    // 保存 NZCV(调试用)
    mrs     x7, nzcv
    nop

    // exit
    mov     x8, #93
    mov     x0, #0
    svc     #0
c 复制代码
(gdb) si
0x000000000040007c in _start ()
=> 0x000000000040007c <_start+4>:       a1 00 80 d2     mov     x1, #0x5                        // #5
(gdb) si
0x0000000000400080 in _start ()
=> 0x0000000000400080 <_start+8>:       22 00 80 d2     mov     x2, #0x1                        // #1
(gdb) si
0x0000000000400084 in _start ()
=> 0x0000000000400084 <_start+12>:      03 00 80 d2     mov     x3, #0x0                        // #0
(gdb) si
0x0000000000400088 in _start ()
=> 0x0000000000400088 <_start+16>:      1f 00 01 eb     cmp     x0, x1
(gdb) si
0x000000000040008c in _start ()
=> 0x000000000040008c <_start+20>:      44 b8 40 fa     ccmp    x2, #0x0, #0x4, lt  // lt = tstop
(gdb) si
0x0000000000400090 in _start ()
=> 0x0000000000400090 <_start+24>:      60 08 40 fa     ccmp    x3, #0x0, #0x0, eq  // eq = none
(gdb) si
0x0000000000400094 in _start ()
=> 0x0000000000400094 <_start+28>:      24 00 80 d2     mov     x4, #0x1                        // #1
(gdb) si
0x0000000000400098 in _start ()
=> 0x0000000000400098 <_start+32>:      05 00 80 d2     mov     x5, #0x0                        // #0
(gdb) si
0x000000000040009c in _start ()
=> 0x000000000040009c <_start+36>:      86 10 85 9a     csel    x6, x4, x5, ne  // ne = any
(gdb) si
0x00000000004000a0 in _start ()
=> 0x00000000004000a0 <_start+40>:      07 42 3b d5     mrs     x7, nzcv
(gdb) si
0x00000000004000a4 in _start ()
=> 0x00000000004000a4 <_start+44>:      1f 20 03 d5     nop
(gdb) info registers x6 x7
x6             0x1                 1
x7             0x0                 0

(1)cmp x0, x1

设置条件:

LT → a < b

GE → a >= b

这里 a < b ,因此是LT

(2)

c 复制代码
ccmp    x2, #0, #0b0100, lt

由于上面是 lt ,因此进行 x2, #0 的比较,x2不等于0,因此是 nq

(3)

c 复制代码
ccmp    x3, #0, #0b0000, eq

由于上面是nq,所以nzcv = #0b0000,即ne,不用比较x3 和 #0。

(4)

c 复制代码
csel    x6, x4, x5, ne

由于上面nzcv = #0b0000,即ne,因此 x6 = x4 = 1。

a b c d 结果
3 5 1 0 1
3 5 0 1 1
5 3 1 1 1
5 3 1 0 0
3 5 1 1 1
相关推荐
HUST2 小时前
C 语言 第七讲:数组和函数实践:扫雷游戏
c语言·开发语言·数据结构·vscode·算法·游戏·c#
玖剹2 小时前
字符串相关题目
c语言·c++·算法·leetcode
jimy12 小时前
程序崩溃free(): double free detected in tcache 2
linux·开发语言·数据结构·链表
松涛和鸣2 小时前
37、UDP网络编程入门
linux·服务器·前端·网络·udp·php
坐吃山猪2 小时前
Python命令行工具Click
linux·开发语言·python
Lueeee.2 小时前
FFMPEG核心结构体
linux·ffmpeg
山土成旧客2 小时前
【Python学习打卡-Day28】类的蓝图:从模板到对象的构建艺术
linux·python·学习
怀旧,2 小时前
【Linux系统编程】14. 库使用与原理(上)
linux·运维·服务器
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之locate命令(实操篇)
linux·运维·服务器·chrome·笔记