ARM64体系结构编程与实践:算术与移位指令

Posted 人邮异步社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARM64体系结构编程与实践:算术与移位指令相关的知识,希望对你有一定的参考价值。

本章思考题

1.请简述NZCV这4个条件标志位的作用。

2.下面两条ADD指令能否编译成功?

add x0, x1, #4096
add x0, x1, #1,LSL 1

3.下面的示例代码中,X0寄存器的值是多少?

mov x1, 0xffffffffffffffff
mov x2, #2
adc x0, x1, x2

4.下面的示例代码中,SUBS指令对PSTATE寄存器有什么影响?

mov x1, 0x3
mov x2, 0x1
subs x0, x1, x2

5.在下面的示例代码中,X0寄存器的值是多少?

mov x1, #3
mov x2, #1
sbc x0, x1, x2

6.检查数组array[0, index −1]是否越界,需要判断两个条件:一是输入值是否大于或等于index,二是输入值是否小于0。如下两条指令可实现数组边界检查的功能,其中X0寄存器的值为数组的边界index,X1寄存器的值为输入值input。请解释这两条指令为什么能实现数组越界检查。

subs xzr,x1,x0
b.hs OutOfInex

7.在下面的示例代码中,W2和W3的值是多少?

ldr w1, =0x8000008a
asr w2, w1, 1
lsr w3, w1, 1

8.如果想在汇编代码中使某些特定的位翻转,该如何操作?

9.设置某个寄存器A的Bit[7, 4]为0x5。下面是C语言的伪代码,用变量val来表示寄存器A的值2,请使用BFI指令来实现。

val &=~ (0xf << 4)
val |= ((u64)0x5 << 4)

10.下面的示例代码中,X0和X1寄存器的值分别是多少?

mov x2, #0x8a
ubfx x0, x2, #4, #4
sbfx x1, x2, #4, #4

11.下面是用C语言来读取pmcr_el0寄存器Bit[15:11]的值,请使用汇编代码来实现。

val = read_sysreg(pmcr_el0)
val = val >> 11;
val &= 0x1f;

本章主要介绍A64指令集中的算术运算和移位指令。

4.1 条件操作码

A64指令集沿用了A32指令集中的条件操作,在PSTATE寄存器中有4个条件标志位,即NZCV,如表4.1所示。

表4.1 条件标志位

条件标志位

描  述

N

负数标志(上一次运算结果为负值)

Z

零结果标志(上一次运算结果为零)

C

进位标志(上一次运算结果发生了无符号数溢出)

V

溢出标志(上一次运算结果发生了有符号数溢出)

常见的条件操作后缀如表4.2所示。

表4.2 常见的条件操作后缀

后  缀

含义(整数运算)

条件标志位

条 件 码

EQ

相等

Z=1

0b0000

NE

不相等

Z=0

0b0001

CS/HS

发生了无符号数溢出

C=1

0b0010

CC/LO

没有发生无符号数溢出

C=0

0b0011

MI

负数

N=1

0b0100

PL

正数或零

N=0

0b0101

VS

溢出

V=1

0b0110

VC

未溢出

V=0

0b0111

HI

无符号数大于

(C=1) && (Z=0)

0b1000

LS

无符号数小于或等于

(C=0) || (Z=1)

0b1001

GE

有符号数大于或等于

N == V

0b1010

LT

有符号数小于

N!=V

0b1011

GT

有符号数大于

(Z==0) && (N==V)

0b1100

LE

有符号数小于或等于

(Z==1) || (N!=V)

0b1101

AL

无条件执行

0b1110

NV

无条件执行

0b1111

4.2 加法与减法指令

下面介绍常见的与加法和减法相关的指令。

4.2.1 ADD指令

普通的加法指令有下面几种用法。

  • 使用立即数的加法。
  • 使用寄存器的加法。
  • 使用移位操作的加法。

1.使用立即数的加法指令

使用立即数的加法指令格式如下。

ADD <Xd|SP>, <Xn|SP>, #<imm>, <shift>

它的作用是把Xn/SP寄存器的值再加上立即数imm,把结果写入Xd/SP寄存器里。指令编码如图4.1所示。

▲图4.1 使用立即数的加法指令的编码

  • Xd/SP:目标寄存器,它对应指令编码中的Rd字段。
  • Xn/SP:源寄存器,它对应指令编码中的Rn字段。
  • imm:立即数,它对应指令编码中的imm12字段。它是一个无符号的立即数,取值范围为0~4095。
  • shift:可选项,用来表示算术左移操作。它对应指令编码中的sh字段。当sh字段为0时,表示“LSL #0”。当sh字段为1时,表示“LSL #12”。

【例4-1】下面是正确的用法。

 add x0, x1, #1 //把x1寄存器的值加上立即数1,结果写入x0寄存器中
 add x0, x1, #1,LSL 12  //把立即数1算术左移12位,然后再加上x1寄存器的值,结果写入x0寄存器中

【例4-2】下面是错误的用法。

add x0, x1, #4096
add x0, x1, #1,LSL 1

汇编器会报如下错误,其中第一条语句中立即数超过了范围,第二条语句中左移的位数只能是0或者12。

test.S: Assembler messages:
test.S: Error: immediate out of range
test.S: Error: shift amount must be 0 or 12 at operand 3 -- 'add x0,x1,#1,LSL 1'

2.使用寄存器的加法指令

使用寄存器的加法指令格式如下。

ADD <Xd|SP>, <Xn|SP>, <R><m>, <extend> #<amount>

这条指令的作用是先把Rm寄存器做一些扩展,例如左移操作,然后再加上Xn/SP寄存器的值,把结果写入Xd/SP寄存器中。

指令编码如图4.2所示。

▲图4.2 使用寄存器的加法指令编码

  • Xd/SP:目标寄存器,它对应指令编码中的Rd字段。
  • Xn/SP:第一个源操作数,它对应指令编码中的Rn字段。
  • R:表示第二个源操作数是64位还是32位的通用寄存器,它对应指令编码中的option字段。当option字段等于X11寄存器的值时,使用64位通用寄存器,其他情况下使用32位通用寄存器。
  • m:通用寄存器编号,和R结合来描述第二个源操作数,可以表示X0~X30或者W0~W30通用寄存器,它对应指令编码中的Rm字段。
  • extend:可选项,用于对第二个源操作数进行扩展计算,它对应指令编码中的option字段。当option = 000时,表示UXTB操作。UXTB表示对8位的数据进行无符号扩展。当option = 001时,表示UXTH操作。UXTB表示对16位的数据进行无符号扩展。当option = 010时,表示UXTW操作。UXTW表示对32位的数据进行无符号扩展。当option = 011时,表示LSL|UXTX操作。LSL表示逻辑左移,UXTX表示对64位数据进行无符号扩展。当option = 100时,表示SXTB操作。SXTB表示对8位的数据进行有符号扩展。当option = 101时,表示SXTH操作。SXTH表示对16位的数据进行有符号扩展。当option = 110时,表示SXTW操作。SXTW表示对32位的数据进行有符号扩展。当option = 111时,表示SXTX操作。SXTX表示对64位的数据进行有符号扩展。
  • amount:当extend为LSL操作时,它的取值范围是0~4,它对应指令编码中的imm3字段。

【例4-3】使用寄存器的加法指令如下。

add x0, x1, x2 //x0 = x1 + x2
add x0, x1, x2, LSL 2 //x0 = x1 + x2 << 2

【例4-4】下面也是使用寄存器的加法指令。

1 mov x1, #1
2 mov x2, #0x108a
3 add x0, x1, x2, UXTB
4 add x0, x1, x2, SXTB

上面的示例代码中,第3行的运行结果为0x8B,因为UXTB对X2寄存器的低8位数据进行无符号扩展,结果为0x8A,然后再加上X1寄存器的值,最终结果为0x8B。

在第4行中,SXTB对X2寄存器的低8位数据进行有符号扩展,结果为0xFFFFFFFFFFFFFF8A,然后再加上X1寄存器的值,最终结果为0xFFFFFFFFFFFFFF8B。

3.使用移位操作的加法指令

使用移位操作的加法指令的格式如下。

ADD <Xd>, <Xn>, <Xm>, <shift> #<amount>

这条指令的作用是先把Xm寄存器做一些移位操作,然后再加上Xn寄存器的值,结果写入Xd寄存器中。

指令编码如图4.3所示。

▲图4.3 使用移位操作的加法指令的编码

  • Xd:目标寄存器,它对应指令编码中的Rd字段。
  • Xn:第一个源操作数,它对应指令编码中的Rn字段。
  • Xm:第二个源操作数,它对应指令编码中的Rm字段。
  • shift:移位操作,它对应指令编码中的shift字段。当shift = 00时,表示LSL操作。当shift = 01时,表示LSR操作。当shift = 10时,表示ASR操作。
  • amount:移位的数量,取值范围是0~63,它对应指令编码中的imm6字段。

【例4-5】如下代码用于实现移位操作加法。

add x0, x1, x2, LSL 3 //x0 = x1 + x2 << 3

【例4-6】下面的代码是错误的。

add x0, x1, x2, LSL 64

amount参数已经超过了取值范围,汇编器会报错,报错的信息如下。

test.S: Assembler messages:
test.S: Error: shift amount out of range 0 to 63 at operand 3 – 'add x0,x1,x1,LSL 64'

4.2.2 ADDS指令

ADDS指令是ADD指令的变种,唯一的区别是指令执行结果会影响PSTATE寄存器的NZCV标志位,例如当计算结果发生无符号数溢出时,C=1。

【例4-7】下面的代码使用了ADDS指令。

mov x1, 0xffffffffffffffff

adds x0, x1, #2

mrs x2, nzcv

X1的值(0xFFFFFFFFFFFFFFFF)加上立即数2一定会触发无符号数溢出,最终X0寄存器的值为1,同时还设置PSTATE寄存器的C标志位为1。我们可以通过读取NZCV寄存器来判断,最终X2寄存器的值为0x20000000,说明第29位的C字段置1,如图4.4所示。

▲图4.4 NZCV寄存器

4.2.3 ADC指令

ADC是进位的加法指令,最终的计算结果需要考虑PSTATE寄存器的C标志位。ADC指令的格式如下。

ADC <Xd>, <Xn>, <Xm>

Xd寄存器的值等于Xn寄存器的值加上Xm寄存器的值加上C,其中C表示PSTATE寄存器的C标志位。

指令编码如图4.5所示。

▲图4.5 ADC指令的编码

  • Xd:目标寄存器,它对应指令编码中的Rd字段。
  • Xn:第一个源操作数,它对应指令编码中的Rn字段。
  • Xm:第二个源操作数,它对应指令编码中的Rm字段。

【例4-8】如下代码使用了ADC指令。

mov x1, 0xffffffffffffffff
mov x2, #2

adc x0, x1, x2

mrs x3, nzcv

ADC指令的计算过程是0xFFFFFFFFFFFFFFFF + 2 + C,因为0xFFFFFFFFFFFFFFFF + 2的过程中已经触发了无符号数溢出,C=1,所以最终计算X0寄存器的值为2。若读取NZCV寄存器,我们发现C标志位也被置位了。

4.2.4 SUB指令

普通的减法指令与加法指令类似,也有下面几种用法。

  • 使用立即数的减法。
  • 使用寄存器的减法。
  • 使用移位操作的减法。

1.使用立即数的减法指令

使用立即数的减法指令格式如下。

SUB <Xd|SP>, <Xn|SP>, #<imm>, <shift>

它的作用是把Xn/SP寄存器的值减去立即数imm,结果写入Xd/SP寄存器里。指令编码如图4.6所示。

▲图4.6 使用立即数的减法指令的编码

  • Xd/SP:目标寄存器,它对应指令编码中的Rd字段。
  • Xn/SP:源寄存器,它对应指令编码中的Rn字段。
  • imm:立即数,它对应指令编码中的imm12字段。它是一个无符号的立即数,取值范围为0~4095。
  • shift:可选项,用来表示算术左移操作。它对应指令编码中的sh字段。当sh字段为0时,表示“LSL #0”。当sh字段为1时,表示“LSL #12”。

【例4-9】如下代码使用了SUB指令。

sub x0, x1, #1 //把x1寄存器的值减去立即数1,结果写入x0寄存器
sub x0, x1, #1,LSL 12  //把立即数1算术左移12位,然后把x1寄存器中的值减去立即数1的结果算术左移
                        //12位的值写入x0寄存器中

【例4-10】下面的用法是错误的。

sub x0, x1, #4097
sub x0, x1, #1,LSL 1

汇编器会报如下错误,其中第一条语句中立即数超过了范围,第二条语句中左移的位数只能是0或者12。

test.S: Assembler messages:
test.S: Error: immediate out of range
test.S: Error: unexpected characters following instruction at operand 3 -- 'sub x0,x1,#1,LSL 1'

2.使用寄存器的减法指令

使用寄存器的减法指令格式如下。

SUB <Xd|SP>, <Xn|SP>, <R><m>, <extend> #<amount>

这条指令的作用是先对Rm寄存器做一些扩展,例如左移操作,然后Xn/SP寄存器的值减Rm寄存器的值,把结果写入Xd/SP寄存器中。

指令编码如图4.7所示。

▲图4.7 使用寄存器的减法指令的编码

  • Xd/SP:目标寄存器,它对应指令编码中的Rd字段。
  • Xn/SP:第一个源操作数,它对应指令编码中的Rn字段。
  • R:表示第二个源操作数是64位还是32位的通用寄存器,它对应指令编码中的option字段。当option字段等于二进制位x11时,使用64位通用寄存器,其他情况下使用32位通用寄存器。
  • m:通用寄存器编号,和R结合来描述第二个源操作数,可以表示X0~X30或者W0~W30通用寄存器,它对应指令编码中的Rm字段。
  • extend:可选项,用于对第二个源操作数进行扩展计算,它对应指令编码中的option字段。当option=000时,表示UXTB操作。UXTB表示对8位的数据进行无符号扩展。当option = 001时,表示UXTH操作。UXTB表示对16位的数据进行无符号扩展。当option = 010时,表示UXTW操作。UXTW表示对32位的数据进行无符号扩展。当option = 011时,表示LSL|UXTX操作。LSL表示逻辑左移,UXTX表示对64位数据进行无符号扩展。当option = 100时,表示SXTB操作。SXTB表示对8位的数据进行有符号扩展。当option = 101时,表示SXTH操作。SXTH表示对16位的数据进行有符号扩展。当option = 110时,表示SXTW操作。SXTW表示对32位的数据进行有符号扩展。当option = 111时,表示SXTX操作。SXTX表示对64位的数据进行有符号扩展。
  • amount:当extend为LSL操作时,它的取值范围是0~4,它对应指令编码中的imm3字段。

【例4-11】如下代码使用了寄存器的减法指令。

sub x0, x1, x2 //x0 = x1 - x2
sub x0, x1, x2, LSL 2 //x0 = x1 - x2 << 2

【例4-12】下面的代码也使用了寄存器的减法指令。

1    mov x1, #1
2    mov x2, #0x108a
3    sub x0, x1, x2, UXTB
4    sub x0, x1, x2, SXTB

上面的示例代码中,UXTB对X2寄存器的低8位数据进行无符号扩展,结果为0x8A,然后再计算1 − 0x8A的值,最终结果为0xFFFFFFFFFFFFFF77。

在第4行中,SXTB对X2寄存器的低8位数据进行有符号扩展,结果为0xFFFFFFFFFFFFFF8A,然后再计算1 − 0xFFFFFFFFFFFFFF8A,最终结果为0x77。

3.使用移位操作的减法指令

使用移位操作的减法指令的格式如下。

SUB <Xd>, <Xn>, <Xm>, <shift> #<amount>

这条指令的作用是先把Xm寄存器做一些移位操作,然后使Xn寄存器中的值减去Xm寄存器中的值,把结果写入Xd寄存器中。

指令编码如图4.8所示。

▲图4.8 使用移位操作的减法指令的编码

  • Xd:目标寄存器,它对应指令编码中的Rd字段。
  • Xn:第一个源操作数,它对应指令编码中的Rn字段。
  • Xm:第二个源操作数,它对应指令编码中的Rm字段。
  • shift:移位操作,它对应指令编码中的shift字段。当shift = 00时,表示LSL操作。当shift=01时,表示LSR操作。当shift=10时,表示ASR操作。
  • amount:移位的数量,取值范围是0~63,它对应指令编码中的imm6字段。

【例4-13】下面的代码用于实现移位操作减法。

add x0, x1, x2, LSL 3 //x0 = x1 - x2 << 3

【例4-14】下面的代码是错误的。

sub x0, x1, x2, LSL 64

上述示例代码中的amount参数超过了取值范围,汇编器会报错,报错的信息如下。

test.S: Assembler messages:
test.S: Error: shift amount out of range 0 to 63 at operand 3 -- 'sub x0,x1,x1,LSL 64'

4.2.5 SUBS指令

SUBS指令是SUB指令的变种,唯一的区别是指令执行结果会影响PSTATE寄存器的NZCV标志位。SUBS指令判断是否影响NZCV标志位的方法比较特别,对应的伪代码如下。

operand2 = NOT(imm);
(result, nzcv) = AddWithCarry(operand1, operand2, '1');
PSTATE.<N,Z,C,V> = nzcv;

首先,把第二个操作数做取反操作。然后,根据式(4-1)计算。

  operand1 + NOT(operand2) + 1  (4-1)

NOT(operand2)表示把operand2按位取反。在这个计算过程中要考虑是否影响NZCV标志位。当计算结果发生无符号数溢出时,C=1;当计算结果为负数时,N=1。

【例4-15】如下代码会导致C标志位为1。

mov x1, 0x3
mov x2, 0x1

subs x0, x1, x2

mrs x3, nzcv

SUBS指令仅仅执行“3 − 1”的操作,为什么会发生无符号溢出呢?

第二个操作数为X2寄存器的值,对应值为1,按位取反之后为0xFFFFFFFFFFFFFFFE。根据计算公式,计算3 + 0xFFFFFFFFFFFFFFFE + 1,这个过程会发生无符号数溢出,因此4个标志位中的C=1,最终计算结果为2。因此,最后一行读取NZCV寄存器的值——0x20000000。

【例4-16】如下代码会导致CZ标志位都置1。

mov x1, 0x3
mov x2, 0x3

subs x0, x1, x2

mrs x3, nzcv

第二个操作数为X2寄存器的值,该值为3,按位取反之后为0xFFFFFFFFFFFFFFFC。根据公式计算3 + 0xFFFFFFFFFFFFFFFC + 1的过程中会发生无符号数溢出,因此C=1。另外,最终结果为0,所以Z=1。

4.2.6 SBC指令

SBC是进位的减法指令,也就是最终的计算结果需要考虑PSTATE寄存器的C标志位。SBC指令的格式如下。

SBC <Xd>, <Xn>, <Xm>

下面是SBC指令中对应的伪代码。

operand2 = NOT(operand2);
(result, -) = AddWithCarry(operand1, operand2, PSTATE.C);
X[d] = result;

所以,SBC指令的计算过程是,首先对第二个操作数做取反操作,然后把第一个操作数、第二个操作数相加,这个过程会影响PSTATE寄存器的C标志位,最后把C标志位加上。

综上所述,SBC指令的计算公式为

Xd = Xn + NOT(Xm) + C  (4-2)

指令编码如图4.9所示。

https://www.epubit.com/center?id=BF106D63-C122-4A3E-9B6A-CC182DFB9F41

▲图4.9 SBC指令的编码

  • Xd:目标寄存器,它对应指令编码中的Rd字段。
  • Xn:第一个源操作数,它对应指令编码中的Rn字段。
  • Xm:第二个源操作数,它对应指令编码中的Rm字段。

【例4-17】如下代码使用了SBC指令。

mov x1, #3
mov x2, #1

sbc x0, x1, x2

mrs x3, nzcv

SBC指令的计算过程是3 + NOT(1) + C。NOT(1)表示对立即数1按位取反,结果为0xFFFFFFFFFFFFFFFE。那么,计算3 + 0xFFFFFFFFFFFFFFFE的过程中会发生无符号数溢出,C=1,再加上C标志位,最后计算结果为2。

4.3 CMP指令

CMP指令用来比较两个数的大小。在A64指令集的实现中,CMP指令内部调用SUBS指令来实现。

1.使用立即数的CMP指令

使用立即数的CMP指令的格式如下。

CMP <Xn|SP>, #<imm>, <shift>

上述指令等同于如下指令。

SUBS XZR, <Xn|SP>, #<imm> , <shift>

2.使用寄存器的CMP指令

使用寄存器的CMP指令的格式如下。

CMP <Xn|SP>, <R><m>, <extend> #<amount>

上述指令等同于如下指令。

SUBS XZR, <Xn|SP>, <R><m>, <extend> #<amount>

3.使用移位操作的CMP指令

使用移位操作的CMP指令的格式如下。

CMP <Xn>, <Xm>, <shift> #<amount>

上述指令等同于如下指令。

SUBS XZR, <Xn>, <Xm> , <shift> #<amount>

4.CMP指令与条件操作后缀

CMP指令常常和跳转指令与条件操作后缀搭配使用,例如条件操作后缀CS表示是否发生了无符号数溢出,即C标志位是否置位,CC表示C标志位没有置位。

【例4-18】使用CMP指令来比较如下两个寄存器。

cmp x1, x2
b.cs  label

CMP指令判断两个寄存器是否触发无符号溢出的计算公式与SUBS指令类似:

X1 + NOT(X2) + 1  (4-3)

如果上述过程中发生了无符号数溢出,那么C标志位会置1,则b.cs指令将会跳转到label处。

【例4-19】下面的代码用来比较3和2两个立即数。

my_test:

     mov x1, #3
     mov x2, #2
1:
     cmp x1, x2
     b.cs 1b

     ret

至于如何比较,需要根据b指令后面的条件操作后缀来定。CS表示判断是否发生无符号数溢出。根据式(4-3)可得,3 + NOT(2) +1,其中NOT(2)把立即数2按位取反,取反后为0xFFFFFFFFFFFFFFFD。3 + 0xFFFFFFFFFFFFFFFD + 1的最终结果为1,这个过程中发生了无符号数溢出,C标志位为1。所以,b.cs的判断条件成立,跳转到标签1处,继续执行。

【例4-20】下面的代码比较X1和X2寄存器的值大小。

my_test:

     mov x1, #3
     mov x2, #2
1:
     cmp x1, x2
     b.ls 1b

     ret

在比较X1和X2寄存器的值大小时,判断条件为LS,表示无符号数小于或者等于。那么,这个比较过程中,我们就不需要判断C标志位了,直接判断X1寄存器的值是否小于或者等于X2寄存器的值即可,因此这里b指令不会跳转到标签1处。

本文截选自《ARM64体系结构编程与实践》第4章:A64指令集2——算术与移位指令

本书旨在详细介绍ARM64体系结构的相关技术。本书首先介绍了ARM64体系结构的基础知识、搭建树莓派实验环境的方法,然后讲述了ARM64指令集中的加载与存储指令、算术与移位指令、比较与跳转等指令以及ARM64指令集中的陷阱,接着讨论了GNU汇编器、链接器、链接脚本、GCC内嵌汇编代码、异常处理、中断处理、GIC-V2,最后剖析了内存管理、高速缓存、缓存一致性、TLB管理、内存屏障指令、原子操作、操作系统等内容。
本书适合嵌入式开发人员阅读。

新人创作打卡挑战赛 发博客就能抽奖!定制产品红包拿不停!

以上是关于ARM64体系结构编程与实践:算术与移位指令的主要内容,如果未能解决你的问题,请参考以下文章

ARM64体系结构编程与实践:基础知识

arm指令周期

arm 汇编指令

shell 编程 05 -- 变量的数值计算实践(readletexprbcdeclareawk杨辉三角)

shell 编程 05 -- 变量的数值计算实践(readletexprbcdeclareawk杨辉三角)

实用汇编指令