程序的机器级表示-跳转

2019-04-28  本文已影响0人  Teech

条件码

除了整数寄存器,cpu还维护着一组单个条件码的寄存器,他们用来描述最近算术和逻辑操作的属性。可以检测这些寄存器来执行分支指令。常见的条件码有:
CF:进位标志。最近的操作产生了最高位产生了进位,可以检查无符号数操作的溢出。
ZF:0标志,最近操作结果为0.
SF:符号标志。最近的操作结果得到了负数。
OF:最近的操作产生了补码溢出(正溢出或者负溢出)
可以举例子说明比如一条ADD指令 t = a + b。
(unsigned)t < (unsigned)a 得出 无符号整数加法溢出了 CF设置了
t = 0 得出 ZF标志设置 产生0了
t < 0 得出 SF标志设置 操作结果得到负数
(a < 0 == b<0) AND (t < 0 != a<0) 可以得出产生了溢出 设置OF。
证明下这个表达式 当产生溢出的时候肯定都是a,b同符号。分两种情况。

  1. 当a > 0,b>0 如果产生溢出,那么 t <= 0
    2.当a< 0,b<0 如果负溢出,那么 t >=0
    表达式拆分后刚好是这两种情况。
    leaq指令不改变条件码,因为是进行地址计算的(这里只能记住就这样了)。 其他指令会修改条件码。
    xor指令,CF标志和OF标志都会被设置,移位操作,进位标志被设置成最后一个被移出的位,溢出操作被设置成0。INC和DEC指令会设置OF标志和ZF标志不会改变CF标志。
    除了上述这些指令还有两类指令CMP和TEST指令只改变条件不改变其他寄存器。

访问条件码

条件码一般不会直接被读取,具体应用的形式

   int c = a > b;
   反汇编后的结果: 
   cmp    -0x8(%rbp),%eax #比较 a > b
   setg   %al                        #如果大于的话 设置%al为1 否则设置0
   movzbl %al,%eax           #拓展字节到双字

setl 表示< \Leftrightarrow 无溢出负数SF&~OF 或者负溢出结果为正数~SF&OF。合并两个表达式SF^OF.
setle 表示\leq \Leftrightarrow (SF^OF) | ZF
setg以及setge分别对上面的表达式取反即可。

跳转指令编码

跳转指令一般都是相对pc指针的编码的。

if(a > 0)
  a= a +1;
反汇编结果如下:
83 7d fc 00             cmpl   $0x0,-0x4(%rbp)
7e 04                   jle    e <main+0xe>
83 45 fc 01             addl   $0x1,-0x4(%rbp)
第二行的7e 04 7e表示jle指令,04表示pc指针+4,不难看出第三行指令占用了4个字节,所以刚好跳转到下一条指令。这段目标代码可以不做改变就可以移动到内存的任意位置。

用条件控制来实现分支

将c语言的表达式翻译成汇编,最常见的方式是结合有条件和无条件跳转。

c语言中 if-else 通用模板为
if (test-exp)
  then-statement
else
  else-statement
对于这种形式,汇编实现往往会用这种形式
  t = test-exp
  if (!t)
    goto false;
  then-statement
  goto done;
false:
  else-statement
done:  

用条件传送实现条件分支

实现条件操作的传统方法是使用控制的条件转移,当条件满足时沿一条执行路径走,不满足时沿另一条路径走。这种方式很低效。一种替代策略是使用“数据的条件转移”,这种方法计算条件操作的两个结果,在 根据条件选择其中一个。

long absdiff(long x,long y){ 
    long result;
    if(x<y)
        result = y-x;
    else
        result = x-y;
    return result;
}
选择O0去编译得到结果
48 89 7d e8             mov    %rdi,-0x18(%rbp)
48 89 75 e0             mov    %rsi,-0x20(%rbp)
48 8b 45 e8             mov    -0x18(%rbp),%rax
48 3b 45 e0             cmp    -0x20(%rbp),%rax
7d 0e                   jge    24 <absdiff+0x24>
48 8b 45 e0             mov    -0x20(%rbp),%rax
48 2b 45 e8             sub    -0x18(%rbp),%rax
48 89 45 f8             mov    %rax,-0x8(%rbp)
eb 0c                   jmp    30 <absdiff+0x30>
48 8b 45 e8             mov    -0x18(%rbp),%rax
48 2b 45 e0             sub    -0x20(%rbp),%rax
48 89 45 f8             mov    %rax,-0x8(%rbp)

选择O1去编译得到的结果
48 89 f2                mov    %rsi,%rdx
48 29 fa                sub    %rdi,%rdx
48 89 f8                mov    %rdi,%rax
48 29 f0                sub    %rsi,%rax
48 39 f7                cmp    %rsi,%rdi
48 0f 4c c2             cmovl  %rdx,%rax

可以观察到O0没有优化,是根据条件转移来控制分支的,O1下优化后,通过条件传送来实现分支的。
这么优化可以带来性能的提成,除了上述指令条数减少外。其实在cpu内部指令并不是串行的,而是每个阶段执行所需操作的一部分,这种重叠指令的方式来获取性能的提升。比如取一条指令的同时计算前面一条指令的算术运算。所以需要流水线里充满待执行的指令序列。当遇到条件跳转时,需要先获取值然后在能决定往哪里跳转。处理器会猜测一个方向,只要猜测可靠,那么流水线里就会充满指令。错误的预测会丢掉之前的工作,然后重新从开始正确的位置开始填充流水线,一个错误预测会有15到30个的时钟周期惩罚。
使用条件传送,可以保证流水线里充满着指令
用条件传送法编译出来的抽象代码描述
v = then-state
ve = else-state
t = test-exp
if(!t)
  v = ve

上一篇 下一篇

猜你喜欢

热点阅读