美文网首页
程序的机器级表示-跳转

程序的机器级表示-跳转

作者: Teech | 来源:发表于2019-04-28 23:26 被阅读0次

    条件码

    除了整数寄存器,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指令只改变条件不改变其他寄存器。
    • CMP S_1,S_2 基于S_2 - S_1 比较
    • TEST S_1,S_2 基于S_1 & S_2 测试
      CMP指令和SUB行为一样的,如果两个数相等ZF会设置成1,其他标志可以确定两个操作数之间的大小关系。
      TEST指的行为和AND指令一样。常用的用法是 testq %rax,%rax来检查%rax是否是负数,零还是正数。或者一个操作码位掩码来指示那些位需要被测试。

    访问条件码

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

    • 根据条件码的某种组合,把一个字节设置成0或者1。比如SET指令的应用。
       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
    
    

    相关文章

      网友评论

          本文标题:程序的机器级表示-跳转

          本文链接:https://www.haomeiwen.com/subject/knxagqtx.html