基本的数据处理指令
本章将介绍一些常用的基本的数据处理指令。
我们常见的数据处理指令包括加、减、乘、除、求余、与、或、非、异或等。大部分的数据处理指令都是二元运算,也就是说,需要将两个操作数进行计算,然后赋值给第三个操作数。因此,这些二元运算指令大都有如下的形式:
opcode dest, source1, source2
加、减、与、或、非、异或
首先是一些常规的、比较简单的计算:
-
加
add dest_reg, src_reg1, src_reg2/imm
-
减
sub dest_reg, src_reg1, src_reg2/imm
-
与
and dest_reg, src_reg1, src_reg2/imm
-
或
orr dest_reg, src_reg1, src_reg2/imm
-
非
mvn dest_reg, src_reg
-
异或
eor dest_reg, src_reg1, src_reg2/imm
值得一提的是,这些操作都没有涉及到符号。在底层的整数一章中,我们提到,使用补码存储整数的好处就是,无论是有符号整数还是无符号整数,其加减运算都不需要区分有无符号。而对与、或、非、异或来说,这些操作都是逐位进行逻辑运算,因此在这些操作中,也不用区分有无符号。
乘、除、求余
-
乘
mul dest_reg, src_reg1, src_reg2 umull dest_reg, src_reg1, src_reg2 smull dest_reg, src_reg1, src_reg2
其中,
mul
指令的三个操作数都是32位寄存器,umull
、smull
的源操作数是32位寄存器,目的操作数是64位寄存器。umull
代表无符号乘法,smull
代表有符号乘法。 -
除
sdiv dest_reg, src_reg1, src_reg2 udiv dest_reg, src_reg1, src_reg2
其中,
sdiv
代表有符号除法,udiv
代表无符号除法。使用这种除法得到的结果,与C语言中的除法操作类似,都是向0取整的整数。因此,对5和2进行
sdiv
,得到的是2;对-5和2进行sdiv
,得到的是-2。 -
求余
A64指令集不提供直接的求余计算。如果我们想求存储有符号整数的寄存器
w1
模w2
的余数,结果存储在w0
中,那么,根据上面介绍的数据处理指令,我们可以这么做:sdiv w0, w1, w2 mul w0, w2, w0 sub w0, w1, w0
我们可以查看codes/9-div.s文件,编译并运行它,使用
echo $?
查看结果是否符合预期。
移位操作
由于计算机底层存储数是用二进制,所以还有一类操作非常常见:移位操作。其中最常用的莫过于逻辑左移:
lsl w0, w1, #2
上述指令的意思是,将w1
内的值逻辑左移2位赋值给w0
。例如,w1
的值是0x12345678
,用二进制表示就是0001 0010 0011 0100 0101 0110 0111 1000
。所谓的逻辑左移,就是将这个二进制数整体向左移动2位(舍弃开头2位),最后的2位用0填充。也就是说,最终w0
为0100 1000 1101 0001 0101 1001 1110 0000
,也就是0x48d159e0
。
逻辑左移操作有什么用呢?我们通过简单的数学知识就可以知道,对于宽度为\(n\)的寄存器来说,将其值\(x\)逻辑左移\(m\)位的运算\(lsl(x, m)\)满足
$$ lsl(x, m)\equiv 2^{m}\cdot x\pmod{2^{n}} $$
也就是说,在汇编层面,如果想将一个数乘2,可以直接逻辑左移1位;乘4就逻辑左移2位。
操作数的可选移位
在之后的章节里,我们会发现,将某个寄存器的值乘以2的倍数往往是一个常见的中间操作。因此,AArch64针对这种情况,对部分指令进行了优化。当我们使用部分指令的时候,可以附带一个移位。例如:
add w0, w1, w2, lsl #2
就是指,将w2
的值乘4,加上w1
的值,赋值给w0
。
当然,并非所有指令的操作数都可以带上可选移位,可以使用可选移位的指令都会在官方文档中注明。目前我们还没有遇到什么可选移位很重要的地方。