本次笔记内容:
08.控制流-1
文章目录
本次笔记还是从C语言入手,来对应汇编语言。本次内容为Control Flow,即跳转。
条件码
基于add的CF, ZF, SF, OF
后文条件码由算数指令隐含设置:
- addl Src, Dest
- 或addq Src, Dest
- 类似的C语言表达式t = a + b (a = Src, b = Dest)
条件码 | 全程 | 意义 |
---|---|---|
CF | Carry(进位) Flag | 可用于检测无符号整数运算的溢出 |
ZF | Zero Flag | set if t == 0 |
SF | Sign Flag | set if t < 0 |
OF | Overflow Flag | set if 补码运算溢出(即带符号整数运算) |
对于OF,在t = a + b中,如果满足如下两种条件中的一种,OF置为1,否则为0:
- a > 0 && b > 0 && t < 0
- a < 0 && b< 0 && t >= 0
比较(Compare)指令
- cmpl Src2, Src1
- cmpq Src2, Src1
cmpl b, a类似于计算a-b(但是不改变目的操作数)
- CF set if carry out from most significant bit,可用于无符号数的比较
- ZF set if a == b
- SF set if (a-b)< 0
- OF set if two’s complement overflow: (a>0 && b<0 && (a-b)<0) || (a<0 && b>0 && (a-b)>0)
测试(Test)指令
- testl Src2, Src1
- testq Src2, src1
计算Src1 & Src2并设置相应的条件码,但是不改变目的操作数。
- ZF set when a & b == 0
- SF set when a&b < 0
读取条件码(SetX)指令
读取当前的条件码(或者某些条件码的组合),并存入目的字节寄存器。
SetX | Condition | Description |
---|---|---|
sete | ZF | Equal / Zero |
setne | ~ZF | Not Equal / Not Zero |
sets | SF | Negative |
setns | ~SF | Nongegative |
setq | ~(SF^OF) & ~ZF | Greater (Signed) |
setge | ~(SF^OF) | Greater of Equal (Signed) |
setl | (SF^OF) | Less (Signed) |
setle | (SF^OF) | ZF |
seta | ~CF & ~ZF | Above (unsigned) |
setb | CF | Below (unsigned) |
例子
读取当前的条件码(或者某些条件码的组合)并存入目的“字节”寄存器:
- 余下的三个字节不会被修改
- 通常使用“movzbl”指令对目的寄存器进行“0”扩展。
int gt (int x, int y)
{
return x > y;
}
其汇编的Body如下:
movl 12(%ebp), %eax # eax = y
cmpl %eax, 8(%ebp) # Compare x : y
setq %al # al = x > y
movzbl %al, %eax # Zero rest of %eax
# mov,z表示0扩展,为b to l
寄存器结构:
如果同样的代码放在x86-64下编译:
int gt (long x, long y)
{
return x > y;
}
long gt (long x, long y)
{
return x > y;
}
x86-64下函数参数:
- x in %rdi
- y in %rsi
Body(same for both):
xorl %eax, %eax # eax = 0 使用异或赋0速度快
cmpq %rsi, %rdi # Compare x : y
setq %al # al = x > y
为什么long编译出来,还是放在32位的%eax中,而非64位的%rax中呢?
拓展:流水设计与微体系结构
这与流水设计、微体系结构背景有关:
因特尔手册:32-bit operands generate a 32-bit result. zero-extended to a 64-bit result in the destination general-purpose register.
为什么?因为部分原因来自于微体系结构(即处理器)内部实现的效率方面的考虑,的是为了消除“部分数据依赖”。
解释如下:
现在的处理器,是基于流水操作的。流水线,让吞吐率变高,虽然总时间会升高(多了交接时间)。
如果流水线很深,有两条指令:读和写,前面的“读”指令不执行结束,后面的“写”指令就无法开始,二者有数据依赖。
这使指令的效率降低。
如果在64位模式做32位操作,对于64位的rax,修改了其低32位eax,而为了不使高32位产生数据依赖(被其他指令依赖),则强制把高32位置为0。
例子如下:
addw %bx, %ax
movl %eax, %ecx
可见,对%ax产生依赖(第一天指令执行完%ax,第二条才能去读%eax)。这种指令有优化的可能性,比如在两条指令间增加有用的其他指令,这样在addw执行同时,先去执行其他指令,而不会触发stop,使movl的流水线等待addw的流水线。
即尽量不用两个数据相关的指令挨在一起。
跳转指令
依赖当前条件码选择下一条执行语句(是否顺序执行)。
jX | Condition | Description |
---|---|---|
jmp | 1 | Unconditional |
je | ZF | Equal / Zero |
jne | ~ZF | Not Equal / Not Zero |
js | SF | Negative |
jns | ~SF | Nongegative |
jg | ~(SF^OF) & ~ZF | Greater (Signed) |
jge | ~(SF^OF) | Greater of Equal (Signed) |
jl | (SF^OF) | Less (Signed) |
jle | (SF^OF) | ZF |
ja | ~CF & ~ZF | Above (unsigned) |
jb | CF | Below (unsigned) |
条件跳转指令实例
int absdiff(int x, int y) {
int result;
if (x > y) {
result = x - y;
} else {
result = y - x;
}
return result;
}
absdiff:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %edx
movl 12(%ebp), %eax
# above are Set Up
cmpl %eax, %edx
jle .L7
subl %eax, %edx
movl %edx, %eax
# above are Body1
.L8:
leave
ret
# Finish
.L7:
subl %edx, %eax
jmp .L8
# above are Body2
条件表达式(条件转移指令)
val = Test ? Then - Expr : Else - Expr;
val = x > y ? x - y : y - x;
Goto Verson:
nt = !Test;
if (nt) goto Else;
val = Then Expr:
Done:
...
Else:
val = Elase - Expr;
goto Done;
为什么引入条件表达式呢?
在x86-64下编译:
int absdiff(int x, int y) {
int result;
if (x > y) {
result = x - y;
} else {
result = y - x;
}
return result;
}
64位编译后:
absdiff: # x in %edi, y in %esi
movl %edi, %eax # v = x
movl %esi, %edx # ve = y
subl %esi, %eax # v -= y
subl %edi, %edx # ve -= x
cmpl %esi, %edi # x : y
cmovle %edx, %eax # v = ve if <= ret
ret
较新的32位Gcc也可以编译出类似的代码(-march=i686)
条件传送指令:
- cmovC src, dest
- 如果条件C成立,将数据从src传送至dest
- 从执行角度来看,比一般的条件跳转指令的效率高(因为其控制流可预测,即条件C是已知的)
在32位使用-march=i686编译后:
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl 8(%ebp), %ecx
movl 12(%ebp), %edx
movl %ecx, %ebx
subl %edx, %ebx
movl %edx, %eax
subl %ecx, %eax
cmpl %edx, %ecx
cmovg %ebx, %eax
popl %ebx
popl %ebp
ret
微体系结构(关于跳转指令)
处理器流水线(五级流水示例):
- Instruction Fetch (IF)
- Read Registers (RD)
- Arithmetic Operation (ALU)
- Memory Access (MEM)
- Write Back (WB)
条件跳转指令往往会引起一定的性能损失,因此需要尽量消除。
因此说,引入cmovle可以一定程度取代条件跳转指令,提升性能。
条件转移指令的局限性
int xgty = 0; xltey = 0;
int absdiff_se(int x, int y) {
int result;
if (x > y) {
xgty++;
result = x - y;
} else {
xltey++;
result = y - x;
}
}
条件执行的语句中,改变了全局变量,因此不能使用条件转移指令。
条件转移指令限制使用的场合:
- Then-Expr或Else-Expr表达式有“副作用”;
- Then-Expr或Else-Expr表达式的计算量较大。
更通俗一些的解释:
条件转移指令相当于C中的:
condition ? exp1 : exp2
意义为,先把exp1和exp2都算一遍,然后根据condition决定要谁。
但是,如果exp1和exp2中都包含对全局变量的操作,最后选了exp1,没有选exp2,难道还要把exp2的全局变量的操作倒回去么?这显然是不合理的,所以说,条件转移指令有“副作用”。
另外,如果exp1和exp2都过于复杂,则两个都进行运算效率低下,还不如使用条件转移指令。