1.算术和逻辑操作
下图是一些整数和逻辑操作
这些操作被分为四组:加载有效地址、一元操作、二元操作和移位。二元操作有两个操作数,而一元操作有一个操作数。
1.1加载有效地址
加载有效地址(load effective address)指令 leaq 实际上是 movq 指令的变形。它的指令形式是从内存读数据到寄存器,但实际上它根本就没有引用内存。它的第一个操作数看 上去是一个内存引用,但该指令并不是从指定的位置读人数据,而是将有效地址写人到目的操作数。
为了说明leaq在编译出的代码中的使用,使用下面示例说明
long scale(long x, long y, long z){
long t = x + 4 * y + 12 *z;
return t; }
编译时,该函数的算术运算以三条leaq指令实现,
long scale(long x, long y, long z)
x in %rdi, y in %rsi, z in %rdx
scale:
leaq (%rdi,%rsi,4), %rax
leaq (%rdx,%rdx,2), %rdx
leaq (%rd%rax,x,4), %rax
ret
这三条指令代表的含义是:
x+4y
z+ 2z
x+4y +12z
leaq指令能执行加法和有限形式的乘法,在编译如上简单的算术表达式时,是很有用处的。
1.2 —元和二元操作
第二组中的操作是一元操作,只有一个操作数,既是源又是目的。
这个操作数可以是一个寄存器,也可以是一个内存位置。如果操作数是寄存器,那么指令会直接操作这个寄存器中的值。如果操作数是内存位置,那么指令会从内存中加载数据到寄存器中,然后执行操作,并将结果写回到同一个内存位置中。
比如说,指令incq(%rsp)会使栈顶的8字节元素加1。这种语法让人想起C语言中的加1运算符(++)和减1运算符(--)。
第三组是二元操作,其中,第二个操作数既是源又是目的。这种语法让人想起C语言中的赋值运算符,例如x-=y。
源操作数是第一个,目的操作数是第二个。例如,指令subq %rax,%rdx 使寄存器%rdx的值减去%rax中的值。
第一个操作数可以是立即数、寄存器或是内存位置。
第二个操作数可以是寄存器或是内存位置。
注意, 当第二个操作数为内存地址时,处理器必须从内存读出值,执行操作,再把结果写回内存。
1.3 移位操作
最后一组是移位操作,先给出移位量,然后第二项给出的是要移位的数。可以进行算术和逻辑右移。
移位量可以是一个立即数,或者放在单字节寄存器%c1中。
示例:salq $4, %rax
将 %rax 中的值左移 4 位
1.4特殊的算术操作
下图描述的是支持产生两个64位数字的全128位乘积以及整数除法的指令。
2.1控制
到目前为止,我们只考虑了直线代码的行为,也就是指令一条接着一条顺序地执行。
c语言中的某些结构,比如条件语句、循环语句和分支语句,要求有条件的执行,根据数据测k的结果来决定操作执行的顺序。机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流。
通过使用jump指令可以改变一组机器代码指令的执行顺序,jump指令指定控制应该被传递到程序的某个其他部分,可能是依赖于某个测试的结果。编译器必须产生构建在这种低级机制基础之上的指令序列,来实现C语言的控制结构。
3.6.1 条件码
除了整数寄存器,CPU还维护着一组单个位的条件码(condition code)寄存器,它们描述了最近的算术或逻辑操作的属性。可以检测这些寄存器来执行条件分支指令。
最常用 的条件码有:
CF:进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作的溢出。
ZF:零标志。最近的操作得出的结果为0。
SF:符号标志。最近的操作得到的结果为负数。
OF:溢出标志。最近的操作导致一个补码溢出一正溢出或负溢出。
leaq 指令不改变任何条件码,因为它是用来进行地址计算的。。对于逻辑操作,例如XOR,进位标志和溢出标志会设 置成0。对于移位操作,进位标志将设置为最后一个被移出的位,而溢出标志设置为0。
2.2 访问条件码
条件码通常不会直接读取,常用的使用方法有三种:
1)可以根据条件码的某种组合, 将一个字节设置为0或者1,2)可以条件跳转到程序的某个其他的部分,3)可以有条件地 传送数据。
set指令
一条SET指令的目的操作数是低位单字节寄存器元素(图3-2)之一,或是一个字节的内存位置,指令会将这个字节设置成0或者1。
2..3 跳转指令
正常执行的情况下,指令按照它们出现的顺序一条一条地执行。跳转(jump)指令会导 致执行切换到程序中一个全新的位置。
示例:
jmp .LI
movq (%rax) ,%rdx
.LI:
指令jmp .LI会导致程序跳过movq指令,而从.LI:指令后面开始执行。
jmp指令是无条件跳转。它可以是直接跳转,即跳转目标是作为指令的一部分编码的;也可以是间接跳转,即跳转目标是从寄存器或内存位置中读出的。
2.4 跳转指令的编码
跳转指令有几种不同的编码,但是最常用都是PC相对的(PC-relative)。也就是,它们会将目标指令 的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。这些地址偏移量可以编 码为1、2或4个字节。
第二种编码方法是给出 “绝对” 地址,用4个字节直接指定目标。 汇编器和链接器会选择适当的跳转目的编码
2.5 用条件控制来实现条件分支
将条件表达式和语句从C语言翻译成机器代码,最常用的方式是结合有条件和无条件跳转。
汇编代码层面的条件控制类似于 c 语言的 goto 语句。汇编语言使用条件码和条件跳转来起到和 c 语言中 if 相似的作用。
2.6 用条件传送来实现条件分支
实现条件操作的传统方法是通过使用控制的条件转移。这种机制简单而通用,但是在现代 处理器上,它可能会非常低效。一种替代的策略是使用数据的条件转移。这种方法计算一个条件操作的两种结果,然后再根据条件是否满足从中选取一个。只有在一些受限制的情况中,这种策略才可行,但是如果可行,就可以用一条简单的条件传送指令来实现它,条件传送指令更符合现代处理器的性能特性。