ARM常用指令(2)

算术与移位指令

加减法指令

  • add指令
  1. 立即数加法指令

    ARM文档描述如下

    ADD Wd|WSP, Wn|WSP, #imm{, shift} ; 32-bit

    ADD Xd|SP, Xn|SP, #imm{, shift} ; 64-bit

    这里要注意的是立即数的范围是0~4095,但是在实际过程中,写出这种语句编译运行也不会报错: \(add \enspace x0, \enspace x0, \enspace 4096\)

    使用汇编与反汇编来看看编译器如何处理的,具体的汇编反汇编命令,可以查看ARM常用指令(1) 中MOV指令讲解。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .arch armv8-a
    .text
    .align 3

    .global test2
    test2:

    add x0, x0, #4095
    add x2, x2, #4096

    ret

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    main.o:     file format elf64-littleaarch64

    Contents of section .text:
    0000 e10300aa 204040f8 220440f9 00fc3f91 .... @@.".@...?.
    0010 42044091 c0035fd6 B.@..._.

    Disassembly of section .text:

    0000000000000000 <test2>:
    0: aa0003e1 orr x1, xzr, x0
    4: f8404020 ldur x0, [x1,#4]
    8: f9400422 ldr x2, [x1,#8]
    c: 913ffc00 add x0, x0, #0xfff
    10: 91400442 add x2, x2, #0x1, lsl #12
    14: d65f03c0 ret

    add x0, x0, #4095 实际上为 add x0, x0, #0xfff

    add x2, x2, #4096 实际上为 add x2, x2, #0x1, lsl #12

    但是如果写成 add x0, x0, #4097编译就会报错 immediate out of range

  2. 寄存器加法指令

    • 加法指令

    1
    2
    add x0, x1, x2, UXTB
    // 类似的extend还有UXTB, UXTH, UXTW, UXTX, SXTB, SXTH, SXTW or SXTX.

    UXT*:零扩展

    SXT*:有符号扩展

    例如:

    1
    2
    3
    4
    5
    6
    7
    mov x1, #1
    mov x2, #0x108a
    add x0, x1, x2, UXTB //(1)
    add x0, x1, x2, SXTB //(2)

    // (1)式结果为0x8B,UXTB取x2寄存器低字节8A做零扩展,与x1寄存器立即数1相加,结果为8B
    // (2)式结果为0xFFFFFF8B,SXTB取x2寄存器低字节8A做有符号扩展,与x1寄存器立即数1相加,结果为FFFFFF8B

    • 移位加法指令

    1
    add x0, x1, x2, LSL 2

    32bit寄存器移位操作数范围为0~31

    64bit寄存器移位操作数范围为0~63

  • 减法指令

    减法指令与加法指令类似,也分为立即数减法指令,寄存器减法指令。

cmp指令

cmp指令内部调用subs指令实现

1
2
3
4
5
cmp x1, x2

=>

subs xzr, x1, x2

移位指令

  • LSL:逻辑左移,最高位丢弃,最低位补0
  • LSR:逻辑右移,最低位丢弃,最高位补0
  • ASR:算术右移,最高位符号扩展,最低位丢弃
  • ROR:循环右移,最低位移动到最高位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ldr w1, =0x8000008a
// w1 二进制表示:0b10000000000000000000000010001010

asr w2, w1, 1
// 算术右移1位,高位按照有符号补充,低位丢弃
// w2 二进制表示:0b11000000000000000000000001000101,16进制表示为0xC0000045

lsr w3, w1, 1
// 逻辑右移1位,最低位丢弃,最高位补0
// w3 二进制表示为:0b01000000000000000000000001000101,16进制表示为0x40000045

lsl w4, w1, 1
// 逻辑左移1位,最高位丢弃,最低位补0
// w4 二进制表示为:0b00000000000000000000000100010100,16进制表示为0x114

ror w5, w1, 2
// 循环右移1位,最低位移动到最高位
// w4 二进制表示为:0b10100000000000000000000000100010,16进制表示为0xA0000022

位操作指令

  • 与操作
1
2
3
4
and x0, x1, x2  // x1 = 6, x2 = 1 => x0 = 0

ands x0, x1, x2 // 影响pstate状态位,x1 = 6, x2 = 1 => x0 = 0
mrs x0, nzcv // x0 = 0x40000,表示z位置为1,说明ands置pstate中z位置为1
  • 或操作
1
orr x0, x1, x2  // x1 = 6, x2 = 1 => x0 = 7
  • 异或操作
1
eor x0, x1, x2  // x1 = 4, x2 = 1 => x0 = 5

异或操作真值表

0 ^ 0 0
0 ^ 1 1
1 ^ 0 1
1 ^ 1 0
  1. 0异或任何数为任何数本身
  2. 1异或任何数为任何数取反
  3. 任何数异或自己为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 实现位翻转
// 翻转0b10100001,第2、3位
10100001 ^ 00000110 = 10100111

// 2. 交换两数
// 交换 a = 10100001和b = 00000110
a = a^b // a = 10100111
b = b^a // b = 10100001
a = a^b // a = 00000110

// 3. 变量设置为0
eor x1, x1

// 4. 判断变量相等
(a ^ b) == 0
  • 位清除
1
2
3
4
// 清除x0中第0、1和3位,保持其余的不变
mov x0, #0x1111
mov x1, #0x1011
bic x0, x0, x1 // x0 = 0x0100

以下为三个目前用的不多的指令

  • CLZ指令
1
2
3
4
// 统计最高位1之前还有几个0
ldr x1, =0x1100000034578000 // x1 二进制为:0b0001000100000000000000000000000000110100010101111000000000000000
clz x0, x1
// x1 最高位为1是第60位,前面还有三个0,因此x0为3
  • BFI指令
1
2
3
4
5
6
7
8
9
10
11
12
13
// 用作bit位插入

mov x0, #0
mov x1, #0x05
bfi x0, x1, #4, #4 //将x0 BIT[4:4+(4-1)]替换为x1[0:0+(4-1)]

// 实际上BFI会被替换成BFM,上述语句等价于下:
movz x0, #0x0
movz x1, #0x5
bfm x0, x1, #60, #3

// 可用来将部分比特位清零
bfi x0, XZR, #0, #3
  • UBFX
1
2
3
4
5
6
7
// 位提取指令
// UBFX 提取后无符号扩展
// SBFX 提取后有符号扩展

mov x1, 0x8a // x1 : 10001010
ubfx x0, x1, #4, #4 // x0 : 00001000
sbfx x0, x1, #4, #4 // x0 : 11111000