ARMv8体系结构基础04:算术和移位指令

目录

1 数据处理指令概述

2 加法指令详解

2.1 ADD指令

2.1.1 ADD(extended register)指令编码分析

2.1.2 ADD(extended register)指令编码验证

2.1.3 ADD(immediate)指令编码分析

2.1.4 ADD(immediate)指令编码验证

2.1.5 ADD(shifted register)指令编码分析

2.1.6 ADD(shifted register)指令编码验证

2.2 ADDS指令

2.2.1 概述

2.2.2 设置标志位的方式

2.3 ADC指令

2.3.1 ADC指令编码分析

2.3.2 ADC指令编码验证

2.3.3 ADC指令使用场景示例

2.4 加法指令实验

2.4.1 实验代码

2.4.2 调试分析

3 减法指令详解

3.1 SUB指令

3.1.1 概述

3.1.2 指令编码分析

3.2 SUBS指令

3.2.1 概述

3.2.2 设置标志位的方式

3.3 SBC指令

3.3.1 SBC指令编码分析

3.3.2 SBC指令编码验证

3.4 减法指令实验

3.4.1 实验代码

3.4.2 调试分析

4 移位指令详解

4.1 概述

4.2 LSL指令

4.2.1 LSL(register)指令编码分析

4.2.2 LSL(register)指令编码验证

4.2.3 LSL(immediate)指令编码分析

4.2.4 LSL(immediate)指令编码验证

4.3 LSR指令

4.3.1 概述

4.3.2 指令编码分析

4.4 ASR指令

4.4.1 概述

4.4.2 指令编码分析

4.5 ROR指令

4.5.1 ROR(register)指令编码分析

4.5.2 ROR(register)指令编码验证

4.5.3 ROR(immediate)指令编码分析

4.5.4 ROR(immediate)指令编码验证

4.6 移位指令实验

4.6.1 实验代码

4.6.2 调试分析

5 位运算指令详解

5.1 AND指令

5.1.1 AND(immediate)指令编码分析

5.1.2 AND(immediate)指令编码验证

5.1.3 AND(shifted register)指令编码分析

5.1.4 AND(shifted register)指令编码验证

5.1.5 ANDS指令概述

5.2 ORR指令

5.2.1 概述

5.2.2 指令编码分析

5.2.3 ORN指令概述

5.3 EOR指令

5.3.1 概述

5.3.2 指令编码分析

5.3.3 EON指令概述

5.4 BIC指令

5.4.1 BIC指令编码分析

5.4.2 BIC指令编码验证

5.4.3 BICS指令概述

5.5 CLZ指令

5.5.1 CLZ指令编码分析

5.5.2 CLZ指令编码验证

5.5.3 CLZ指令使用场景示例

5.5.4 CLS指令概述

5.6 位运算指令实验

5.6.1 实验代码

5.6.2 调试分析

6 位段操作指令详解

6.1 概述

6.2 位段提取指令

6.2.1 UBFX指令编码分析

6.2.2 UBFX指令编码验证

6.2.3 SBFX指令编码分析

6.2.4 SBFX指令编码验证

6.3 位段插入指令

6.3.1 BFI指令编码分析

6.3.2 BFI指令编码验证

6.3.3 UBFIZ指令编码分析

6.3.4 UBFIZ指令编码验证

6.3.5 BFXIL指令编码分析

6.3.6 BFXIL指令编码验证

6.4 位段清零指令

6.4.1 BFC指令编码分析

6.4.2 BFC指令编码验证

6.5 位段操作指令实验

6.5.1 实验代码

6.5.2 调试分析

6.6 实验:读取寄存器指定字段

6.6.1 实验要求

6.6.2 实验代码

6.6.3 调试分析

7 扩展指令详解

7.1 概述

7.2 字节扩展指令

7.2.1 UXTB指令编码分析

7.2.2  UXTB指令编码验证

7.2.3 SXTB指令编码分析

7.2.4 SXTB指令编码验证

7.3 半字扩展指令

7.3.1 UXTH指令编码分析

7.3.2 UXTH指令编码验证

7.3.3 SXTH指令编码分析

7.3.4 SXTH指令编码验证

7.4 字扩展指令

7.4.1 SXTW指令编码分析

7.4.2 SXTW指令编码验证

7.5 扩展指令实验

7.5.1 实验代码

7.5.2 调试分析

8 反转指令详解

8.1 概述

8.2 位反转指令

8.2.1 RBIT指令编码分析

8.2.2 RBIT指令编码验证

8.3 字节反转指令

8.3.1 REV指令编码分析

8.3.2 REV指令编码验证

8.4 半字内字节反转

8.4.1 REV16指令编码分析

8.4.2 REV16指令编码验证

8.5 字内字节反转

8.5.1 REV32指令编码分析

8.5.2 REV32指令编码验证

8.6 反转指令实验

8.6.1 实验代码

8.6.2 调试分析

9 乘法除法指令简介

9.1 概述

9.2 乘法指令特性

9.3 除法指令特性


1 数据处理指令概述

1. 数据处理指令(data processing instruction)用于进行最基础的算术和逻辑运算

2. 数据处理指令一般有1个目标寄存器和2个源操作数,指令格式如下,

Instruction Rd, Rn, Operand2

其中第二操作数Operand2可以是寄存器、修改后的寄存器值(modified registere.g. 对寄存器值进行移位操作)或立即数

3. 指令中使用的寄存器可以是64位(Xn),也可以是32位(Wn)

4. 数据处理指令进行的操作包括,

① 算术与逻辑运算

② 移动(move)与移位(shift)

③ 符号扩展(sign extension)与零扩展(zero extension)

④ 位(bit)与位域(bitfield)操作

⑤ 条件比较与数据处理

说明:数据处理指令中的乘除法指令格式与上文所述的一般格式不同,可以认为是数据处理指令中的特例,详见下文分析

2 加法指令详解

2.1 ADD指令

根据不同的第二操作数构成方式,ADD指令共有extended register / immediate / shifted register三种编码方式

2.1.1 ADDextended register)指令编码分析

1. ADD(extended register)指令将2个源操作数相加的结果写入目标寄存器,其中2个源操作数构成如下,

① 寄存器值

② 符号扩展或零扩展后的寄存器值,同时还可以对扩展后的值进行逻辑左移(LSL)

计算过程可表示为如下形式,

Rd = Rn + LSL(extend(Rm), amount)

2. 指令中的<R>和<extend>字段被联合编码在option字段,

① <R>字段用于指定第二操作数的寄存器长度,该寄存器中的数值将被扩展与移位,option字段编码含义如下,

② <extend>字段用于指定对第二操作数寄存器值进行的扩展操作,option字段编码含义如下,

可以看到,对应0bx11编码的扩展方式分别是UXTX和SXTX,此时要扩展的源操作数是64,所以需要使用64位寄存器Xn

③ 对于0b011编码,可以表示LSL或UXTX,具体的区分方式如下,

  • 如果<Rd>或<Rn>为栈寄存器SP,且option字段编码为0b011,则此时表示LSL。如果此时imm3字段编码的移位值为0,则此处的LSL可以省略(也可以理解为,在该指令中,对于SP寄存器没有扩展操作)
  • 如果<Rd>或<Rn>为通用寄存器,且option字段编码为0b011,则此时表示UXTX。同时需要注意,此时在指令中必须指定<extend>字段

④ 从<R><m>寄存器扩展的参数可以是字节(byte,1B)、半字(halfword,2B)、字(word,4B)、四字(doubleword,8B)。在指令中,前3种使用32位寄存器Wn,最后一种使用64位寄存器Xn

这里顺带回顾一下ARMv8体系结构中的数据类型,

需要注意的是,无论扩展的源操作数是1 / 2 / 4 / 8B,扩展的目标都是将第二操作数扩展为64

3. 指令中的#<amount>字段被编码在imm3字段,

① #<amount>表示将寄存器值进行扩展后进行LSL的位数,合法值为0 ~ 4,默认值为0

② 当指令中的<extend>字段指定为扩展操作时,#<amount>字段可选,也就是说既可以进行LSL运算,也可以不进行LSL运算(或者说LSL位数为0)

③ 当指令中的<extend>字段指定为LSL时,则必须带有#<amount>字段

④ 当指令中不包含#<extend>字段时,则必须不包含#<amount>字段

说明1:指令中同时不包含#<extend>字段与#<amount>字段时,指令格式如下,

ADD <Xd|SP>, <Xn|SP>, <R><m>

需要特别注意的是,ADDextended register)指令可以将SP寄存器作为操作数。在这种情况下,当操作数为SP寄存器时,会被编译为ADD(extended register)指令;当操作数为通用寄存器时,则会被编译为ADD(shifted register)指令,详见下文验证

说明2:ADDextended register)指令中,只能进行LSL移位操作。如果要进行其他移位操作,则会被编码为ADD(extended register)指令。毕竟ADDextended register)指令的重点是进行扩展操作

2.1.2 ADDextended register)指令编码验证

2.1.2.1 仅有扩展操作

编译如下指令,

add x0, x1, x2, uxtw

对应机器码如下,可见此时默认的LSL移位值为0,也就是仅扩展,不移位

2.1.2.2 同时包含扩展和LSL移位操作

编译如下指令,

add x0, x1, x2, uxtw #3

对应机器码如下,

说明:合法的移位值

根据手册,合法的移位值为0 ~ 4,编译如下移位值不合法的指令,

add x0, x1, x2, uxtw #5

编译会报出移位值越界错误

2.1.2.3 仅有LSL移位操作

将#<extend>字段指定为LSL,编译如下指令,

add x0, x1, x2, lsl #3

对应机器码如下,可见此时被编码为ADD(shifted register)指令,而不是ADD(extended register)指令

上文中我们提到过,ADDextended register)指令可以将SP寄存器作为操作数,而ADD(shifted register)指令却不可以。因此,编译如下指令,

add sp, sp, x0, lsl #3

对应机器码如下,可见此时被编码为ADD(extend register)指令。也就是说,仅有LSL移位的ADDextended register)指令是专门为操作sp寄存器准备的

说明1:关于option字段编码为0b011的情况

根据指令编码,当option字段编码为0b011时,第二操作数只能是64位寄存器Xn,后续操作为LSL移位。那么此时如果第二操作数为32位寄存器Wn,指令将如何编码呢?编译如下指令,

add sp, sp, w0, lsl #3

可见指令被编译为先扩展后LSL移位

说明2:根据手册,当#<extend>字段被指定为LSL时,必须有#<amount>字段。其实从逻辑上就可以看出,这是必须的要求,如果不包含#<amount>字段则编译报错

2.1.2.4 不包含#<extend>字段

编译如下指令,其实可以猜到,此时编译出来的不是ADD(extended register)指令

add x0, x1, x2

对应机器码如下,可见被编译为ADD(shifted register)指令

同样的,如果目的操作数或第一操作数为sp寄存器,则会编译为ADD(extended register)指令。编译如下指令,

add sp, sp, x0

对应机器码如下,可见被编译为ADD(extended register)指令

同样的,如果将指令中的第二操作数寄存器改为32位Wn,则会将被编译为扩展操作

add sp, sp, w0

2.1.3 ADDimmediate)指令编码分析

1. ADD(immediate)指令将2个源操作数相加的结果写入目标寄存器,其中2个源操作数构成如下,

① 寄存器值

② 立即数,同时还可以对该立即数进行逻辑左移(LSL)

计算过程可表示为如下形式,

Rd = Rn + shift(imm)

2. 指令中的#<imm>字段被编码在imm12字段

立即数共12位,且为无符号立即数,因此范围为0 ~ 4095

3. 指令中的<shift>字段被编码在sh字段

sh字段仅有1位,所以只有2种移位模式,

① 当sh = 0,表示LSL #0,这也是默认值

② 当sh = 1,表示LSL #12

2.1.4 ADDimmediate)指令编码验证

2.1.4.1 通用寄存器无移位操作

编译如下指令,

add x0, x1, #16

对应机器码如下,

说明:指令中合法的立即数范围为0 ~ 4095

① 如果立即数范围大于4095,编译如下指令

add x0, x1, #4096

对应机器码如下,可见由于我们给出的立即数正好可以通过lsl #12得到,因此可以编译

如果立即数的范围无法通过lsl #12得到,编译会报出立即数越界错误

add x0, x1, #4097

② 如果立即数范围小于0,编译如下指令

add x0, x1, #-1

对应机器码如下,可见在立即数可表示的情况下,会被编译为相应的减法指令

2.1.4.2 通用寄存器有移位操作

编译如下指令,

add x0, x1, #16, lsl #12

对应机器码如下,

说明:合法的移位值只有lsl #0和lsl #12,使用其他移位值,编译会报出移位值越界错误

add x0, x1, #16, lsl #11

2.1.4.3 SP寄存器操作

编译如下指令,

add sp, sp, #16

对应机器码如下,

2.1.5 ADDshifted register)指令编码分析

1. ADD(shifted register)指令将2个源操作数相加的结果写入目标寄存器,其中2个源操作数构成如下,

① 寄存器值

② 移位后的寄存器值

计算过程可表示为如下形式,

Rd = Rn + shift(Rm, amount)

2. 指令中的<shift>字段被编码在shift字段,表示移位类型,其中默认移位类型为LSL

可见此处是没有ROR移位类型的

3. 指令中的#<amount>字段被编码在imm6字段

立即数共6位,在64位指令形式中,合法值为0 ~ 64;在32位指令形式中,合法值为0 ~ 31

2.1.6 ADDshifted register)指令编码验证

编译如下指令,

add x0, x1, x2, lsr #1

对应机器码如下,

2.2 ADDS指令

2.2.1 概述

根据不同的第二操作数构成方式,ADDS指令也有extended register / immediate / shifted register三种编码方式

ADDS指令与相应的ADD指令基本相同,差别仅在于ADDS指令会根据计算结果设置标志位。下面以ADDS(immediate)指令进行说明

可见与ADD(immediate)指令相比,就是指令编码中的S位被置为1

2.2.2 设置标志位的方式

ADDS(immediate)指令操作的伪代码如下,

可见指令会根据加法计算的结果设置pstate中的NZCV

说明1:NZCV标志位

Flag

Name

Description

N

Negative

负数标志位

设置为当前计算结果的最高位,在计算结果为二进制补码的情况下,N = 1表示结果为负数;N = 0表示结果非负数

Z

Zero

零标志位

如果计算结果为0,则Z = 1;否则Z = 0

C

Carry

进位标志位(无符号数溢出标志位)

当计算结果发生无符号溢出时,C = 1;否则C = 0

在移位运算中,C也可以表示最后移出的比特位值

V

Overflow

有符号溢出标志位

当计算结果发生有符号溢出时,V = 1;否则V = 0

说明2:其实指令本身并不区分有无符号数,而是将计算结果按照有无符号数分别判断一遍,具体的含义由程序员判断。以ADDS指令为例,在对应的伪操作AddWithCarry中,会分别以有符号数和无符号数方式进行计算

说明3:加法的进位与无符号溢出

算术上的进位

当无符号数的计算结果超过64位所能表示的范围时,最高有效位发生算术上的进位。例如0xFFFFFFFFFFFFFFFF + 0x1 = 0x1 0000000000000000,最高有效位就发生了算术上的进位。只是由于有效位的限制,计算结果为0x0000000000000000

条件标志位上的溢出

根据AddWithCarry的实现,通过判断unsigned_sum<N-1:0>与unsigned_sum是否相等,来判断是否发生无符号溢出。因此当发生最高有效位的进位时,也会发生条件标志位上的溢出

③ 也就是说,对于加法运算,算术上的进位与条件标志位上的溢出是一致的

2.3 ADC指令

2.3.1 ADC指令编码分析

ADC(ADD with Carry)指令只有一种编码方式,

ADC指令将2个源寄存器值以及C标志位相加的结果写入目标寄存器,计算过程可表示为如下形式,

Rd = Rn + Rm + C

2.3.2 ADC指令编码验证

编译如下指令,

adc x0, x1, x2

对应机器码如下,

2.3.3 ADC指令使用场景示例

当进行超过64位的加法计算时,可以使用ADC指令。假设进行128位加法运算,其中被加数存储在X1:X0寄存器中,加数存储在X3:X2寄存器中,计算结果存储在X1:X0寄存器,则可以用如下指令实现

# 进行低64位运算,并根据计算结果设置标志寄存器

adds x0, x0, x2

# 结合低64位的运算结果是否有进位,进行高64位运算

adc x1, x1, x3

说明:ADCS指令

ADCS指令在ADC指令的基础上,根据计算结果设置标志位

2.4 加法指令实验

2.4.1 实验代码

第1组指令用于验证ADD指令不会修改标志位

第2组指令用于验证ADDS指令对标志位的影响

第3组指令用于验证ADC指令对C标志位的使用

说明:代码中将NZCV寄存器清零,是为了排除初始值的干扰,下面给出NZCV寄存器描述

2.4.2 调试分析

1. ADD指令执行后,发生无符号溢出,x2中的值为0

在ADD指令执行前,NZCV寄存器的值为0x0

2. 在ADD指令执行后,再次获取NZCV寄存器的值,仍为0x0,可见ADD指令确实不会影响标志位

3. ADDS指令执行后,发生无符号溢出,x2中的值为0

在ADDS指令执行前,NZCV寄存器的值为0x0

4. ADDS指令执行后,再次获取NZCV寄存器的值,此时值为0x60000000。对照NZCV寄存器描述,此时,

① Z = 1,表示计算结果为0

② C = 1,表示发生条件标志位上的无符号溢出

5. ADC指令执行后,x1寄存器值为0x1,等于(0 + 0 + C标志位)

3 减法指令详解

3.1 SUB指令

3.1.1 概述

根据不同的第二操作数构成方式,SUB指令也有extended register / immediate / shifted register三种编码方式

SUB指令中操作数的编码方式与相应的ADD指令基本相同,差别仅在于SUB指令进行的是减法运算

3.1.2 指令编码分析

3.1.2.1 SUBextended registration

SUB(extended register)指令将2个源操作数相减的结果写入目标寄存器,其中2个源操作数构成如下,

① 寄存器值

② 符号扩展或零扩展后的寄存器值,同时还可以对扩展后的值进行逻辑左移(LSL)

计算过程可表示为如下形式,

Rd = Rn - LSL(extend(Rm), amount)

说明:SUB(extended register)指令也可以将SP寄存器作为操作数

3.1.2.2 SUBimmediate

SUB(immediate)指令将2个源操作数相减的结果写入目标寄存器,其中2个源操作数构成如下,

① 寄存器值

② 立即数,同时还可以对该立即数进行逻辑左移(LSL)

计算过程可表示为如下形式,

Rd = Rn - shift(imm)

3.1.2.3 SUBshifted register

SUB(shifted register)指令将2个源操作数相减的结果写入目标寄存器,其中2个源操作数构成如下,

① 寄存器值

② 移位后的寄存器值

计算过程可表示为如下形式,

Rd = Rn - shift(Rm, amount)

3.2 SUBS指令

3.2.1 概述

根据不同的第二操作数构成方式,SUBS指令也有extended register / immediate / shifted register三种编码方式

SUBS指令与相应的SUB指令基本相同,差别仅在于SUBS指令会根据计算结果设置标志位。下面以SUBS(immediate)指令进行说明

可见与SUB(immediate)指令相比,就是指令编码中的S位被置为1

3.2.2 设置标志位的方式

SUBS(immediate)指令操作的伪代码如下,

1. 将第二操作数取反

2. 要计算的内容为operand1 - operand2,传递给AddWithCarry函数后,实际进行的运算为

operand1 + NOT(operand2) + 1

3. 可见NOT(operand2) + 1就是operand2的补码,而operand2 + operand2的补码 = 0x0并且最高有效位溢出。那么就可以得到如下推论,

① 如果abs(operand1) < abs(operand2),则不会发生最高有效位溢出。此时,

  • 算术上发生借位
  • 条件标志位上没有溢出

② 如果abs(operand1) >= abs(operand2),则会发生最高有效位溢出。此时,

  • 算术上没有发生借位
  • 条件标志位上发生溢出

因此,对于SUBS指令,算术上的借位与条件标志位的溢出是相反的

说明:比较指令CMP就是通过SUBS指令实现的,CMP指令改变标志位,但是不保存减法运算结果。关于比较指令的详细内容,可参考后续笔记

3.3 SBC指令

3.3.1 SBC指令编码分析

SBC(Subtract with Carry)指令只有一种编码方式,

SBC指令将2个源寄存器值相减并加上C标志位取反的结果写入目标寄存器,计算过程可表示为如下形式,

Rd = Rn - Rm - 1 + C

说明1:实现算术上借位的正确语义

① 要说明这里对C标志位的使用方式,就需要先说明SBC指令的一般使用场景。当进行超过64位的减法计算时,可以使用SBC指令。假设进行128位减法计算,其中被减数存储在X1:X0寄存器中,减数存储在X3:X2寄存器中,计算结果存储在X1:X0寄存器中,则可以用如下指令实现

# 进行低64位运算,并根据计算结果设置标志寄存器

subs x0, x0, x2

# 结合低64位的运算结果是否有进位,进行高64位运算

sbc x1, x1, x3

也就是说,SBC指令中要正确体现减法中算术上的借位

② 根据上文中对SUBS指令的分析,对于SUBS指令,算术上的借位与条件标志位的溢出是相反的,因此在SBC指令中通过将C标志位取反来确保算术上借位的正确语义

说明2:SBCS指令

SBCS指令在SBC指令的基础上,根据计算结果设置标志位

3.3.2 SBC指令编码验证

编译如下指令,

sbc x0, x1, x2

对应机器码如下,

3.4 减法指令实验

3.4.1 实验代码

第1组指令用于验证SUB指令不会修改标志位

第2组指令用于验证SUBS指令对标志位的影响

第3组指令用于验证SBC指令对C标志位的使用

3.4.2 调试分析

1. SUB指令执行后,x2中的值为-1,此时会发生算术上的借位

在SUB指令执行前,NZCV寄存器的值为0x0

2. 在SUB指令执行后,再次获取NZCV寄存器的值,仍为0x0,可见SUB指令确实不会影响标志位

3. SUBS指令执行后,x2中的值为-1,仍会发生算术上的借位

在SUBS指令执行前,NZCV寄存器的值为0x0

4. SUBS指令执行后,再次获取NZCV寄存器的值,此时值为0x80000000。对照NZCV寄存器描述,此时

① N = 1,表示计算结果最高有效位为1;也就是以二进制补码表示计算结果时,计算结果为负数

② C = 0,表示没有发生条件标志位上的无符号溢出。这也验证了在SUBS指令中,算术上的借位与标志位上的无符号溢出确实是相反的

5. SBC指令执行后,x1寄存器值为0xffffffffffffffff,等于(0 - 0 - 1 + C标志位)

4 移位指令详解

4.1 概述

ARMv8体系结构中,共提供4种移位指令,他们的功能如下,

1. LSL(Logical Shift Left,逻辑左移)

每向左移动一位,相当于将操作数乘2

2. LSR(Logical Shift Right,逻辑右移)

① 每向右移动一位,最高位补0

② 每向右移动一位,相当于将操作数作为无符号数除2

3. ASR(Arithmetic Shift Right, 算术右移)

① 每向右移动一位,最高位补原来的符号位

② 每向右移动一位,相当于将操作数作为有符号数除2

4. ROR(Rotate Right,循环右移)

说明1:被移位操作数存储

被移位操作数存储在寄存器中,可以使用64位寄存器Xn,也可以使用32位寄存器Wn

说明2:移位值存储

移位值可以是立即数,也可以存储在寄存器中

如果移位值是立即数,那么移位的最大值为被移位操作数寄存器长度减1(也就是说,如果被移位操作数存储在64位寄存器Xn中,移位值最大为63;如果被移位操作数存储在32位寄存器Wn中,移位值最大为31)

如果移位值存储在寄存器中,那么移位值取自寄存器的低5位(当被移位操作数存储在32位寄存器Wn中)或低6位(当被移位操作数存储在64位寄存器Xn中)

说明3:由于逻辑左移和算术左移的操作相同,所以ARMv8体系结构中没有单独定义算术左移指令

4.2 LSL指令

根据不同的移位值构成方式,LSL指令分为register和immediate两种编码方式

4.2.1 LSLregister)指令编码分析

1. LSL(register)指令计算过程可表示为如下形式,

Rd = LSL(Rn, Rm)

2. 在64位形式中,移位值取自<Xm>寄存器的低6位,范围为0 ~ 63。相当于将<Xm>寄存器的值对64取模

3. LSL(register)指令是LSLV(Logical Shift Left Variable)指令的别名(alias),也就是说,一般都是通过LSL(register)指令的方式来使用LSLV指令

从指令编码分析,LSLV指令的行为确实与LSL(register)指令相同

4.2.2 LSLregister)指令编码验证

编译如下指令,

lsl x0, x1, x2

对应机器码如下,

4.2.3 LSLimmediate)指令编码分析

1. LSL(immediate)指令计算过程可表示为如下形式,

Rd = LSL(Rn, shift)

2. 在64位形式中,移位值立即数的范围为0 ~ 63

说明:关于immr与imms编码

① 在LSL(register)指令条目中,并没有详细说明移位值#<shift>字段的编码方式。由于LSL(register)指令是通过UBFM(Unsigned Bitfield Move)指令实现的,所以我们分析该指令的编码方式

② 从指令编码分析,UBFM指令非常灵活,除了用于实现LSL(immediate)指令,还用于实现LSR(immediate)/ UBFIZ / UBFX / UXTB / UXTH指令

③ UBFM指令中#<immr>和#<imms>字段的编码方式如下,

对于64位形式,

  • <immr>字段表示循环右移的位数,范围为0 ~ 63
  • <imms>字段表示要从源操作数移动的最左边的位号,范围为0 ~ 63

④ 对#<immr>和#<imms>字段的理解

  • 如果#<imms> >= #<immr>,则将源寄存器中从<immr>开始的(<imms> - <immr> + 1)个位的位段拷贝到目标寄存器的最低有效位
  • 如果#<imms> < #<immr>,则将(<imms> + 1)位的位段从源寄存器的最低有效位拷贝到目的寄存器的(regsize - <immr>)位的位置。其中regsize为目标寄存器的位数

如此抽象地理解UBFM指令是很困难的,下面通过实例进行说明。但UBFM指令的要义,就是通过循环右移和位段拷贝的组合方式来实现那些别名命令

4.2.4 LSLimmediate)指令编码验证

编译如下指令,

lsl x0, x1, #63

对应机器码如下,

可见逻辑左移63位是通过将源寄存器的低1位拷贝到目的寄存器的第63位实现的

作为对照,编译如下指令,

lsl x0, x1, #0

对应机器码如下,

再来看一下逻辑左移1位的编码方式,编译如下指令,

lsl x0, x1, #1

对应机器码如下,

通过上面的3个实例,就可以总结出UBFM指令如何使用<immr>和<imms>字段,

1. 首先使用<imms>字段从源寄存器中选出要参与循环右移 + 拷贝的位段,<imms>为参与的最左边位号。如果是0,则表示只有最低1位参与;如果是63,则表示所有64位参与

可以认为将上述截取出的位段拷贝到一个中间寄存器的最低有效位,不足的高位补零

2. 之后使用<immr>字段对上一步选出的位段进行循环右移操作,可以认为循环右移操作在上述的中间寄存器中完成

3. 将循环右移的结果拷贝到目标寄存器中

说明:有了上面的基础,就很容易理解LSL(immediate)指令与UBFM指令的对应关系了,

对于逻辑左移<shift>位,

① 保留的参与循环右移 + 拷贝的位段的最左边位号为(63 - <shift>)

② 要达到逻辑左移<shift>位的效果,需要循环右移(64 - <shift>)位

说明:合法的移位值

根据手册,合法的移位值为0 ~ 63,编译如下移位值不合法的指令,

lsl x0, x1, #64

lsl x0, x1, #-1

编译会报出立即数越界错误

4.3 LSR指令

4.3.1 概述

根据不同的移位值构成方式,LSR指令分也为register和immediate两种编码方式

LSR指令中移位值的编码方式与相应的LSL指令基本相同

4.3.2 指令编码分析

4.3.2.1 LSRregister

1. LSR(register)指令计算过程可表示为如下形式,

Rd = LSR(Rn, Rm)

2. 在64位形式中,移位值取自<Xm>寄存器的低6位,范围为0 ~ 63。相当于将<Xm>寄存器的值对64取模

3. LSR(register)指令是LSRV(Logical Shift Right Variable)指令的别名(alias),也就是说,一般都是通过LSR(register)指令的方式来使用LSRV指令

从指令编码分析,LSRV指令的行为确实与LSR(register)指令相同

4.3.2.2 LSRimmediate

1. LSR(immediate)指令计算过程可表示为如下形式,

Rd = LSR(Rn, shift)

2. 在64位形式中,移位值立即数的范围为0 ~ 63

3. LSR(immediate)指令也是通过UBFM指令实现的,他们的对应关系如下,

① <imms> = 63,即保留的参与循环右移 + 拷贝的位段的最左边位号为63,也就是整个源寄存器予以保留

② <immr> = <shift>,即循环右移<shift>位

③ 需要注意的是,这里并不是将循环右移后的所有值拷贝到目标寄存器,而是只拷贝了未被右移的部分,从而实现正确的语义

说明:补充一个LSR(immediate)指令的编码验证

编译如下指令,

lsr x0, x1, #1

对应机器码如下,

4.4 ASR指令

4.4.1 概述

根据不同的移位值构成方式,ASR指令分也为register和immediate两种编码方式

ASR指令中移位值的编码方式与相应的LSR指令思路相同

4.4.2 指令编码分析

4.4.2.1 ASRregister

1. ASR(register)指令计算过程可表示为如下形式,

Rd = ASR(Rn, Rm)

2. 在64位形式中,移位值取自<Xm>寄存器的低6位,范围为0 ~ 63。相当于将<Xm>寄存器的值对64取模

3. ASR(register)指令是ASRV(Logical Shift Right Variable)指令的别名(alias),也就是说,一般都是通过ASR(register)指令的方式来使用ASRV指令

4.4.2.2 ASRimmediate

1. ASR(immediate)指令计算过程可表示为如下形式,

Rd = ASR(Rn, shift)

2. 在64位形式中,移位值立即数的范围为0 ~ 63

3. ASR(immediate)指令是通过SBFM指令实现的,也是通过循环右移 + 位段拷贝的方式实现功能

4.5 ROR指令

根据不同的移位值构成方式,ROR指令分也为register和immediate两种编码方式

4.5.1 RORregister)指令编码分析

1. ROR(register)指令计算过程可表示为如下形式,

Rd = ROR(Rn, Rm)

2. 在64位形式中,移位值取自<Xm>寄存器的低6位,范围为0 ~ 63。相当于将<Xm>寄存器的值对64取模

3. ROR(register)指令是RORV(Logical Shift Left Variable)指令的别名(alias),也就是说,一般都是通过ROR(register)指令的方式来使用RORV指令

从指令编码分析,RORV指令的行为确实与ROR(register)指令相同

4.5.2 RORregister)指令编码验证

编译如下指令,

ror x0, x1, x2

对应机器码如下,

4.5.3 RORimmediate)指令编码分析

1. ROR(immediate)指令计算过程可表示为如下形式,

Rd = ROR(Rs, shift)

2. 在64位形式中,移位值立即数的范围为0 ~ 63

说明:ROR(immediate)指令源操作数寄存器编码方式

① 在ROR(immediate)指令条目中,并没有详细说明源操作数寄存器的编码方式。由于ROR(immediate)指令是通过EXTR(Extract Register)指令实现的,所以我们分析该指令的编码方式

② EXTR指令从一对寄存器中提取出一个寄存器的值

③ 在64位形式中,EXTR指令就是从<Xn>和<Xm>寄存器中提取出一个64位寄存器的值,并写入目的寄存器。具体的提取方式如下,

可见EXTR指令先将2个源操作数拼接为一个128位的值,然后从#<lsb>字段指定的位置开始截取其中的64位

④ 这样就很容易理解ROR(immediate)指令与EXTR指令的对照关系了,此处是将源操作数寄存器<Xs>重复拼接,之后从#<shift>字段开始截取

通过下面的示意图,可以直观地理解ROR(immediate)指令的工作方式,以datasize = 4为例,

4.5.4 RORimmediate)指令编码验证

编译如下指令,

ror x0, x1, #63

对应机器码如下,

说明:合法的移位值

根据手册,合法的移位值为0 ~ 63,编译如下移位值不合法的指令,

ror x0, x1, #-1

ror x0, x1, #64

编译会报出立即数越界错误

4.6 移位指令实验

4.6.1 实验代码

指令用于验证当存储在寄存器中的移位值越界时,基于register的移位指令的工作方式

4.6.2 调试分析

LSL指令执行后,x0寄存器中的值为0x1,相当于对x1寄存器没有进行移位。这是因为x2寄存器中存储的移位值为128,而LSL(register)指令从x2寄存器的低6位截取移位值,也就是0

同样的,如果将存储在x2寄存器中的移位值修改为-1,LSL(register)指令从x2寄存器的低6位截取移位值就是63

5 位运算指令详解

5.1 AND指令

根据不同的第二操作数构成方式,AND指令共有immediate / shifted register两种种编码方式

5.1.1 ANDimmediate)指令编码分析

1. AND(immediate)指令计算过程可表示为如下形式,

Rd = Rn AND imm

2. 根据手册,在64位形式中,指令中的#<imm>字段被编码在"N:imms:immr"字段,但是具体的编码方式并没有说明,需要验证

3. ANDimmediate)指令可以将SP寄存器作为目的操作数

5.1.2 ANDimmediate)指令编码验证

编译如下指令,

and x0, x1, #2048 // 2048 = 0b1000 0000 0000

对应机器码如下,

可见立即数bitmask的编码形式并不是简单的存储在"N:imms:immr"字段,根据手册,指令中#<imm>字段的编码方式如下。此处的DecodeBitMasks函数尚未理解

5.1.3 ANDshifted register)指令编码分析

1. AND(shifted register)指令的计算过程可表示为如下形式,

Rd = Rn AND shift(Rm, amount)

2. 指令中的<shift>字段被编码在shift字段,表示移位类型,其中默认移位类型为LSL

3. 指令中的#<amount>字段被编码在imm6字段

立即数共6位,在64位指令形式中,合法值为0 ~ 64;在32位指令形式中,合法值为0 ~ 31

4. AND(shifted register)指令不能将SP寄存器作为操作数

5.1.4 ANDshifted register)指令编码验证

编译如下指令,

and x0, x1, x2, lsr #1

对应机器码如下,

5.1.5 ANDS指令概述

1. 根据不同的第二操作数构成方式,ANDS指令也有immediate / shifted register两种编码方式

2. ANDS指令与AND指令编码方式类似,差别在于ANDS指令会根据计算结果设置标志位,其中主要影响Z标志位。具体的设置方式如下,

3. TST指令通过ANDS指令实现,相当于进行逻辑与运算,但是丢弃计算结果,也有immediate / shifted register两种编码方式

① TST(immediate)指令

② TST(shifted register)指令

5.2 ORR指令

5.2.1 概述

根据不同的第二操作数构成方式,ORR指令也有immediate / shifted register两种种编码方式

ORR指令中操作数的编码方式与相应的AND指令基本相同,差别仅在于ORR指令进行的是逻辑或运算

5.2.2 指令编码分析

5.2.2.1 ORRimmediate

ORR(immediate)指令计算过程可表示为如下形式,

Rd = Rn OR imm

5.2.2.2 ORRshifted register

ORR(shifted register)指令的计算过程可表示为如下形式,

Rd = Rn OR shift(Rm, amount)

5.2.3 ORN指令概述

在进行逻辑或运算的系列指令中,还有一条ORN(shifted register)指令(OR NOT),该指令将源操作数按位取反后,再进行逻辑或运算

ORN(shifted register)指令的编码如下,

ORN(shifted register)指令的计算过程可表示为如下形式,

Rd = Rn OR NOT shift(Rm, amount)

5.3 EOR指令

5.3.1 概述

根据不同的第二操作数构成方式,EOR(Exclusive OR)指令也有immediate / shifted register两种种编码方式

ORR指令中操作数的编码方式与相应的AND指令基本相同,差别仅在于EOR指令进行的是逻辑异或运算

说明1:异或运算的特点

① 任何数和0异或后保持不变

0 ^ 0 = 0

1 ^ 0 = 1

② 任何数和1异或后取反

0 ^ 1 = 1

1 ^ 1 = 0

说明2:异或运算使用场景示例

根据上述异或运算的特点,就有了如下的使用场景示例,

① 将数值中的指定位翻转

只要将要翻转的位与1异或即可

② 将变量清零

a = a ^ a; // 可以将变量a清零



// 在汇编语言中,也可以使用异或运算将寄存器清零

eor x0, x0, x0

③ 不借助中间变量交换2个数

a = a ^ b;

// 计算中利用了变量与自己异或的值为0

b = b ^ a; // b = b ^ a = b ^ (a ^ b) = a ^ (b ^ b) = a ^ 0 = a

a = a ^ b; // a = a ^ b = (a ^ b) ^ a = b ^ (a ^ a) = b ^ 0 = b

④ 判断两个数是否相等

if ((a ^ b) == 0)

5.3.2 指令编码分析

5.3.2.1 EORimmediate

EOR(immediate)指令计算过程可表示为如下形式,

Rd = Rn EOR imm

5.3.2.2 EORshifted register

EOR(shifted register)指令的计算过程可表示为如下形式,

Rd = Rn EOR shift(Rm, amount)

5.3.3 EON指令概述

与ORN(shifted register)指令类似,也有一条EON(shifted register)指令(Exclusive OR NOT),该指令将源操作数按位取反后,再进行逻辑异或运算

EON(shifted register)指令的编码如下,

EON(shifted register)指令的计算过程可表示为如下形式,

Rd = Rn EOR NOT shift(Rm, amount)

5.4 BIC指令

5.4.1 BIC指令编码分析

BIC(Bit Clear)指令只有一种编码方式,

BIC(shifted register)指令的计算过程可表示为如下形式,可见使用BIC指令可以将数值中的指定位清零

Rd = Rn AND NOT shift(Rm, amount)

说明:BIC指令是和ORN / RON指令对应的操作,因此也就没有AND NOT指令了

5.4.2 BIC指令编码验证

编译如下指令,

bic x0, x1, x2, ror #1

对应机器码如下,

5.4.3 BICS指令概述

1. BICS指令也只有一种编码方式,

2. BICS指令与BIC指令编码方式类似,差别在于BICS指令会根据计算结果设置标志位,其中主要影响Z标志位。具体的设置方式如下,

5.5 CLZ指令

5.5.1 CLZ指令编码分析

CLZ(Count Leading Zeros)指令只有一种编码方式,

CLZ(shifted register)指令的计算过程可表示为如下形式,可见CLZ指令用于计算数值中前导零的个数

Rd = CLZ(Rn)

5.5.2 CLZ指令编码验证

编译如下指令,

clz x0, x1

对应机器码如下,

5.5.3 CLZ指令使用场景示例

在FreeRTOS操作系统中,可以使用任务就绪位图来表示对应的优先级是否有就绪任务。在FreeRTOS中,表示任务优先级的数值越大,任务的优先级越高

因此,通过CLZ指令计算任务就绪位图uxTopReadyPriority的前导零个数,然后通过(31 - 前导零个数)就可以快速计算出当前有任务就绪的最高优先级。详情可参考05. 支持多优先级 chapter 2

5.5.4 CLS指令概述

CLS(Count Leading Sign)指令只有一种编码方式,

CLS(Count Leading Sign)指令的计算过程可表示为如下形式,可见CLS指令用于计算数值中与MSB数值相同的前导位数

Rd = CLS(Rn)

5.6 位运算指令实验

5.6.1 实验代码

第1组指令用于验证ANDS指令对标志位的影响

第2组指令用于验证CLZ指令的操作

5.6.2 调试分析

1. 第1次ANDS指令执行完成后,逻辑与运算的结果为0x1,此时Z = 0

2. 第2次ANDS指令执行完成后,逻辑与运算的结果为0x0,此时Z = 1

3. CLZ指令执行完成后,可计算出0x80000的前导零为48个

6 位段操作指令详解

6.1 概述

位段操作指令可以分为如下4类,

1. 位段提取指令

从源寄存器中截取指定位段,将其拷贝到目的寄存器的LSB,并对源寄存器的高位进行符号扩展或零扩展

2. 位段插入指令

从源寄存器中截取指定位段,将其拷贝到目的寄存器的指定位置,其实是用截取的位段替换插入位置的位段

3. 位段清零指令

将目的寄存器中的指定位段清零

说明1:位段操作图示

说明2:位段操作均通过BFM / UBFM / SBFM指令实现

6.2 位段提取指令

6.2.1 UBFX指令编码分析

UBFX(Unsigned Bitfield Extract)指令将源寄存器<Xn>中从#<lsb>位开始,宽度为#<width>的位段拷贝到目的寄存器<Xd>LSB,并且在目的寄存器的高位进行零扩展。其中,

① #<lsb>在64位形式中,有效值为0 ~ 63;在32位形式中,有效值为0 ~ 31

② #<width>在64位形式中,有效值为(1 ~ 64 - #<lsb>);在32位形式中,有效值为(1 ~ 32 - #<lsb>)

6.2.2 UBFX指令编码验证

编译如下指令,

ubfx x0, x1, #12, #4

对应机器码如下,

6.2.3 SBFX指令编码分析

SBFX(Signed Bitfield Extract)指令的位段截取与拷贝操作与UBFX指令相同,只是在目的寄存器中根据截取位段的最高位进行符号扩展

6.2.4 SBFX指令编码验证

编译如下指令,

sbfx x0, x1, #12, #4

对应机器码如下,

6.3 位段插入指令

6.3.1 BFI指令编码分析

BFI(Bitfield Insert)指令将源寄存器<Xn>中从LSB开始,宽度为#<width>的位段拷贝到目的寄存器<Xd>#<lsb>位开始的位置。其中,

① #<lsb>在64位形式中,有效值为0 ~ 63;在32位形式中,有效值为0 ~ 31

② #<width>在64位形式中,有效值为(1 ~ 64 - #<lsb>);在32位形式中,有效值为(1 ~ 32 - #<lsb>)

6.3.2 BFI指令编码验证

编译如下指令,

bfi x0, x1, #12, #4

对应机器码如下,

6.3.3 UBFIZ指令编码分析

UBFIZ(Unsigned Bitfield Insert in Zeros)指令将源寄存器的LSB开始,宽度为#<width>的位段拷贝到目的寄存器<Xd>#<lsb>位开始的位置,并且将目的寄存器的其他位清零。相当于在BFI的基础上增加了其余位的清零操作。其中,

① #<lsb>在64位形式中,有效值为0 ~ 63;在32位形式中,有效值为0 ~ 31

② #<width>在64位形式中,有效值为(1 ~ 64 - #<lsb>);在32位形式中,有效值为(1 ~ 32 - #<lsb>)

6.3.4 UBFIZ指令编码验证

编译如下指令,

ubfi x0, x1, #12, #4

对应机器码如下,

6.3.5 BFXIL指令编码分析

BFXIL(Bitfield Extract and Insert Low)指令将源寄存器<Xn>中从#<lsb>位开始,宽度为#<width>的位段拷贝到目的寄存器<Xd>LSB,并且保持其他位不变。其中,

① #<lsb>在64位形式中,有效值为0 ~ 63;在32位形式中,有效值为0 ~ 31

② #<width>在64位形式中,有效值为(1 ~ 64 - #<lsb>);在32位形式中,有效值为(1 ~ 32 - #<lsb>)

6.3.6 BFXIL指令编码验证

编译如下指令,

bfxil x0, x1, #12, #4

对应机器码如下,

6.4 位段清零指令

6.4.1 BFC指令编码分析

BFC(Bitfield Clear)指令将目的寄存器<Xd>中从#<lsb>位开始,宽度位#<width>的位段清零。其中

① #<lsb>在64位形式中,有效值为0 ~ 63;在32位形式中,有效值为0 ~ 31

② #<width>在64位形式中,有效值为(1 ~ 64 - #<lsb>);在32位形式中,有效值为(1 ~ 32 - #<lsb>)

6.4.2 BFC指令编码验证

编译如下指令,

bfc x0, #12, #4

在编译时报出如下错误,

根据手册,BFC指令由ARMv8.2版本引入。我们使用的交叉工具链版本为5.5.0,主要支持ARMv8.1的指令集,因此无法编译这条指令。这也体现了在项目中适时更新编译器的重要性,否则无法使用体系结构扩展引入的新特性

参考资料:Linaro Releases

说明1:为了验证上面的分析,我们使用更新版本的交叉工具链进行编译

使用如下命令,可以编译BFC指令,这里的关键是使用-march=armv8.2-a编译选项,如果不指定该选项,使用7.5.0版本的交叉工具链也无法编译BFC指令

而之前使用的5.5.0版本的交叉工具链,尚未支持-march=armv8.2-a编译选项

① -march=armv8.2-a

编译选项格式:-march=name

对于ARM体系结构,-march编译选项用于指定编译时使用的体系结构(或者说是指令集)。而且指定的指令集是向下扩展的,例如指定为armv8.2-a,则表示同时使能armv8.1-a的指令集以及armv8.2-a的扩展

② -MMD

只使用用户提供的头文件,而不使用系统提供的头文件

参考资料:[BUG] clang++: Error: selected processor does not support `bfc w19,#0,#16' · Issue #1322 · android/ndk · GitHub

说明2:BFC指令编码验证

说明3:位段操作指令编码规律总结

位段操作指令中,除了BFC指令,其余指令的格式均为,

instruct <Xd>, <Xn>. #<lsb>, #<width>



// BFC指令只是没有源操作数寄存器

BFC <Xd>, #<lsb>, #<width>

由于指定起始位置的#<lsb>字段只有一个,因此要么用于指定从源寄存器中截取位段的起始位置,要么用于指定拷贝到目的寄存器中的起始位置;而截取的长度都是使用#<width>字段指定

① 对于有位段提取操作的指令(UBFX / SBFX / BFXIL / BFC)

#<lsb>字段用于指定从源寄存器中截取位段的起始位置,那么拷贝到目的寄存器中的起始位置就只能使用LSB

② 对于没有位段提取操作的指令(BFI / UBFIZ)

#<lsb>字段用于指定拷贝到目的寄存器中的起始位置,那么源寄存器中截取位段的起始位置就只能使用LSB

6.5 位段操作指令实验

6.5.1 实验代码

6.5.2 调试分析

1. ubfx指令执行后,可见截取拷贝之后,进行的是零扩展

2. sbfx指令执行后,可见截取拷贝之后,进行的是符号扩展

3. bfi指令执行后

4. ubfiz指令执行后,可见在截取拷贝之后,其余位被清零

5. bfxil指令执行后

6.6 实验:读取寄存器指定字段

6.6.1 实验要求

使用位段提取指令读取PMCR_EL0寄存器的N字段,该字段表示PE实现的event counter数量

如果使用C语言实现该功能,代码如下,

val = read_sysreg(pmcr_el0);

val >>= 11;

val &= 0x1f

6.6.2 实验代码

6.6.3 调试分析

1. 读取的pmcr_el0寄存器值如下

2. 截取的N字段值如下

7 扩展指令详解

7.1 概述

1. 扩展指令用于将字节(byte,1B)、半字(halfword,2B)和字(word,4B)扩展到目的寄存器的长度

2. 目的寄存器可以是32位(Wn),也可以是64位(Xn);但是源操作数寄存器均为32位(Wn)

3. 可以进行有符号扩展,也可以进行无符号扩展

7.2 字节扩展指令

7.2.1 UXTB指令编码分析

1. UXTB(Unsigned Extend Byte)指令从源寄存器<Wn>中提取LSB的1B,对其进行零扩展,并将结果写入目的寄存器<Wd>。指令的计算过程可表示为如下形式

Wd = ZeroExtend(Wn<7:0>)

2. UXTB指令只有32位形式,源操作数寄存器和目的寄存器均为32位(Wn

7.2.2  UXTB指令编码验证

编译如下指令,

uxtb w0, w1

对应机器码如下,

7.2.3 SXTB指令编码分析

1. SXTB(Signed Extend Byte)指令从源寄存器<Wn>中提取LSB的1B,对其进行符号扩展,并将结果写入目的寄存器<Rd>。指令的计算过程可表示为如下形式

Rd = SignExtend(Wn<7:0>)

2. SXTB指令有32位和64位形式,区别在于目的寄存器的长度

7.2.4 SXTB指令编码验证

编译如下指令,

sxtb x0, w1

对应机器码如下,

7.3 半字扩展指令

7.3.1 UXTH指令编码分析

1. UXTH(Unsigned Extend Halfword)指令从源寄存器<Wn>中提取LSB的2B,对其进行零扩展,并将结果写入目的寄存器<Wd>。指令的计算过程可表示为如下形式

Wd = ZeroExtend(Wn<15:0>)

2. UXTH指令只有32位形式,源操作数寄存器和目的寄存器均为32位(Wn

7.3.2 UXTH指令编码验证

编译如下指令,

uxth w0, w1

对应机器码如下,

7.3.3 SXTH指令编码分析

1. SXTH(Signed Extend Halfword)指令从源寄存器<Wn>中提取LSB的2B,对其进行符号扩展,并将结果写入目的寄存器<Rd>。指令的计算过程可表示为如下形式

Rd = SignExtend(Wn<15:0>)

2. SXTH指令有32位和64位形式,区别在于目的寄存器的长度

7.3.4 SXTH指令编码验证

编译如下指令,

sxth x0, w1

对应机器码如下,

7.4 字扩展指令

7.4.1 SXTW指令编码分析

1. SXTW(Signed Extend Halfword)指令从源寄存器<Wn>中提取LSB的2B,对其进行符号扩展,并将结果写入目的寄存器<Rd>。指令的计算过程可表示为如下形式

Xd = SignExtend(Wn<31:0>)

2. SXTW指令只有64位形式,源操作数为32位(Wn),目的操作数为64位(Xn

7.4.2 SXTW指令编码验证

编译如下指令,

sxtw x0, w1

对应机器码如下,

7.5 扩展指令实验

7.5.1 实验代码

7.5.2 调试分析

1. uxtb指令执行后

2. sxtb指令执行后,可见由于目的寄存器为w0,所以只符号扩展到4B

3. uxth指令执行后

4. sxth指令执行后,这里同样也只符号扩展到4B

5. sxtx指令执行后

8 反转指令详解

8.1 概述

反转指令用于实现位和字节反转,可以分为如下4类,

1. 位反转

将寄存器中操作数的比特位顺序反转,这里是指从MSB到LSB的比特位顺序反转,不是指每一个比特位的值反转

2. 字节反转

将寄存器中操作数的字节顺序反转

3. 半字内字节反转

将寄存器中的操作数按半字进行字节反转

4. 字内字节反转

将寄存器中的操作数按字进行字节反转

8.2 位反转指令

8.2.1 RBIT指令编码分析

RBIT(Reverse Bits)指令将源寄存器<Rn>中的操作数比特顺序反转后写入目的寄存器<Rd>

8.2.2 RBIT指令编码验证

编译如下指令,

rbit x0, x1

对应机器码如下,

8.3 字节反转指令

8.3.1 REV指令编码分析

REV(Reverse Bytes)指令将寄存器<Rn>中的操作数字节顺序反转后写入目的寄存器<Rd>

说明:REV指令可以用在整型数值的大小端转换中

8.3.2 REV指令编码验证

编译如下指令,

rev x0, x1

对应机器码如下,

说明:REV64指令

手册中还有一条REV64指令,该指令也是进行字节反转,只是操作数只能是64位(Xn)。该指令的编码与64位形式的REV指令相同

该指令由ARMv8.2扩展引入,使用当前交叉工具链无法编译

8.4 半字内字节反转

8.4.1 REV16指令编码分析

REV16(Reverse bytes in 16-bit halfword)指令将寄存器<Rn>中的操作数按半字进行字节反转后写入目的寄存器<Rd>

8.4.2 REV16指令编码验证

编译如下指令,

rev16 x0, x1

对应机器码如下,

8.5 字内字节反转

8.5.1 REV32指令编码分析

1. REV32(Reverse bytes in 32-bit words)指令将寄存器<Xn>中的操作数按字进行字节反转后写入目的寄存器<Xd>

2. REV32指令只有64位形式

8.5.2 REV32指令编码验证

编译如下指令,

rev32 x0, x1

对应机器码如下,

8.6 反转指令实验

8.6.1 实验代码

8.6.2 调试分析

1. rbit指令执行后,此处x0寄存器的值省略了最高位的0,完整形式为0x00ff000000000000

2. rev指令执行后

3. rev16指令执行后

4. rev32指令执行后

9 乘法除法指令简介

9.1 概述

由于乘法除法指令在Linux内核中并不常用(乘法指令在算法实现中常用),此处仅给出简要介绍。AArch64中定义的乘法除法指令如下,

9.2 乘法指令特性

1. MUL类乘法指令返回和操作数长度相同的结果,即32位乘法产生32位结果;64位乘法产生64位结果

2. 很显然,MUL类乘法是很容易溢出的。此时可以使用MULL(Long)类乘法指令,这类指令返回长类型乘法结果,即32位乘法产生64位结果(这种乘法类似X86体系结构中的乘法)。这类乘法被称作widening multiply

3. 对于MULL类乘法,64位乘法产生128位结果,从事需要SMULH + SMULL共2条指令实现

4. 在乘法计算的基础上,可以进行乘加、乘减、乘取反等运算

9.3 除法指令特性

1. 支持32位和64位的有符号除法和无符号除法

2. 溢出和除零不会引发异常

3. 只有SDIV有符号除法指令会产生溢出

猜你喜欢

转载自blog.csdn.net/chenchengwudi/article/details/123828339
今日推荐