作者:Yaong
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
 
if-then-else、loop控制语句的后端实现
 
本文是通过代码而来,主要记录了在SIMD指令集上,编译器后端对控制语句(if-then-else、loop)的指令生成方法。
 
引言:
"A unique feature of most GPU’s is that they are designed to run many different instances of the same program in lock step in order to reduce the size of the scheduling hardware by sharing it between many different “cores.” When control flow diverges, meaning that when two different instances (fragments, vertices, etc.) branch in different directions, then the GPU will take both sides of the branch. For example, if both thread 1 and thread 2 are currently in block A, and thread 1 wants to branch to block B while thread 2 wants to branch to block C, then the GPU will first branch to block B with thread 1 enabled and thread 2 disabled, and then when execution reaches a predefined “merge block,” the GPU will jump back to block C while flipping the enabled threads and run until the merge block is reached, at which point the control flow has converged and both thread 1 and thread 2 can be enabled."
 
if-then-else语句
 
进入到if-then-else语句块,转换为branch指令时,因为使用的是SIMD指令,所以各个channel的跳转控制流程可能会出现分歧。
在此我们先假设condition等于true,执行if-then语句块;condition等于false,执行else语句。
当各个channel的condition相同时,各个channel不会出现分歧,各个channel的跳转地址(代码块)也是相同的,这种情况下,当condition等于true时,所有channel执行if-then语句块,反之,condition等于false,所以channel执行else语句。
当各个channel的condition条件不一样,那么各个channel就会出现分歧,if、else两个语句块都需要被按条件被执行一次。对于condition等于1的channel,需要执行if-then语句块,而不能执行else语句;对于condition等于false的channel,要执行else语句,而不能执行if-then语句块。
最后,进入到汇合点后,所有的channel又可以并行的执行相同的指令了。
 
如何处理上述的两种情况呢?
首先这需要机器指令级别上的支持,不同的处理器有不同的实现。
来看看VC4提供的指令方案:
1.每个channel有独立的标志位,比如,N、Z、C
2.运算指令的执行结果能设置每个channel的标志位
3.指令支持条件执行
4.branch指令支持跳转条件,且支持各个channel标志位间的逻辑运算
 
具体来看.
 
伪代码:
sf: set flag
zs: zero set
zc: zero clear
 
01:    mov    execute, 0
 
02:    OR.sf    tmp0, condition, 0
03:    mov.zs    execute, @else_block
04:    mov.sf    null, execute
05:    Branch.all_zero_clear    @else_block
 
06:    then_block:
07:    …
08:    mov.sf    null, execute
09:    mov.zs    execute, @after_block
10:    sub.sf  tmp1, execute, @after_block
11:    branch.all_zero_set    @after_block
12:    end_then:
 
13:    else_block:
14:    sub.sf tmp2, execute, @else_block
15:    mov.zs execute, 0
16:    …
17:    end_else:
 
18: after_block:
 
伪代码中condition表示if的条件“if(condition)”,而execute实时记录了各个channel的执行条件。
 
当各个channel的condition相等时,这种情况相对简单些,各个channel执行的流程是相同的,要么执行if-then语句块,那么执行else语句。
当各个channels的condition值不相等时,“if-then”“else”两个语句块,均要被执行一次,但是,不是每个channel都要执行,只有在进入到语句块中execute等于零的channel才能执行其中的指令,而非零值的channel不能执行语句块中的指令(非零值是等于else语句块的index号,指向执行完当前语句块后,接下来要进入执行的语句块)。具体来看,执行流程首先(PC指针)进入到“if”基本块中,对于execute值等于0的channel会执行基本块中的指令,非0的要跳过基本块中的所有指令。在if-then语句块执行后,执行过“if”语句的channel,就不能再执行else语句的指令,所以在if-then语句块结束后需要对execute更新,在执行if-then语句块时execute等于0的channel,需要把execute更新为after block index,即执行完else基本块后的基本块的index号,非0的channel的execute需要更新为0,表明在接下来的else基本块中需要执行指令,所以在执行流程(PC指针)进入else基本块中后,同样的execute等于零的channel才会执行指令,非零不执行.
 
进入某个语句块前,需要对execute进行测试,进入某个语句块前execute保存了各个channel的将要执行的语句块的地址。
对满足if语句块的channel的退出if语句块时,需要将execute更新为下一个执行的语句块地址,即@after_block。
 
回到伪代码:
 
当我们开始处理if-then-else语句时,我们先判断执行条件,以取得下一句指令的入口,这时可能出现3种情况:
1.所有channel的condition等于0,则跳转到else_block
2.所有channel的condition不等于0,则执行if_block,并且在if_block执行完毕后,要跳过else_block。
3.各个channel的condition不全为零,按标志位,先执行if_block,再执行else_block。
 
1.将execute初始化为0,execute保存了各个channel的执行条件,在进入if-then语句块后,对execute等于0的channel,要执行if-then语句块中的指令,在进入else语句后,这些channel就不能再执行其中的else语句中的指令了。对不执行if-then语句块的channel,对应的execute值等于@else_block。
2.将condition与0做比较,并且运算结果会设置各个channel的标志位,如果condition等于0,会将对应channel的 Z标志位设置为0,否则设置为1。
3.语句3中,mov会利用语句2中对Z标志位的设置结果,更新各个channel的execute值。如果Z标志位等于0,则将@else_block的地址跟新到execute中。
4.语句4,根据execute的值,更新Z标志位
5.语句5根据以上4句的运算结果,如果全部channel的execute都不等于0,则跳转到@else_block地址。
   具体的说,如果全部channel的condition等于0,那么所有channel的execute都等于@else_block,所以语句4的执行后,所有channel的Z标志位都不为零,语句5满足跳转条件,跳转到@else_block;
   如果全部channel的condition都等于1,那么所有channel的execute等于0,所以语句4的执行后,所有channel的Z标志位都为零,语句5的跳转条件不满足,进入@then_block;
   如果各个channel的condition不全相等,那么,对于condition等于0的channel的execute值就等于@else_block;而对于1的channel,execute的值等于0,所以语句4的执行后,所有channel的Z标志位不全为零,语句5的跳转条件不满足,进入@then_block。
 
6.从语句6开始是if-then语句块。一旦能进入if-then语句块中,就需要依据各个channel的execute值,来判断指令能否被执行。所以每条指令执行前都要用execute更新一次Z标志位(暂不考虑优化),且每条指令都要加上条件执行码,例如:
    Mov.sf    null, execute
Add.zs tmp4, tmp3, tmp2 Mov.sf null, execute
Sub.zs tmp5, tmp3, tmp1

如前文所述,对于execute等于0的channel,执行“Mov.sf    null, execute”后,Z标志位置位,接下来的“Add.zs     tmp4, tmp3, tmp2”就能被执行。

7.在if-then语句块执行完毕后,离开if-then语句块前,需要判断执行流程的下一个入口。如5中所述,能进入到if-then语句块中有两种情况,分别对应不同的出口。
  1. 如果是全部channel的condition等于1,这种情况下我们接下来不需要执行else语句了。因为各个channel的execute均等于0,所以语句8执行后会把每个channel的Z标志位置位,紧接着语句9,也就把每个channel的execute值都更新为@after_block,在语句10测试跳转条件,与@after_block相减,结果为0,所以各个channel的Z标志位被置位,语句11的跳转条件是全部Z标志位置位,此时条件满足,即跳转到after_block,也就结束了if-then-else语句的处理。
  2. 如果不是全部channel的condition都等于1,接下来我们需要进入到else语句,执行非零的channel。语句8会把execute等于0的channel的Z标志位置位,语句9把Z标志位置位的channel的execute更新为@after_blcok,其余保存不变,语句10做条件测试的结果,不能满足语句11的跳转条件。接下进入else语句,并且这时各个channel的execute的值等于@else_blcok或@after_block。

8.能进入到else语句,也有两种情况,并且else语句中的指令也需要做if-then语句块中相同的处理,依据execute更新Z标准位,每条执行要交条件执行码。
   在执行else语句中的指令前,我们需要更新execute的值。只有在进入else语句前execute值等于@else_block的channel才能执行else语句中的指令。所以语句14先对execute做测试,与@else_block相减,对于execute等于@else_block的channel的N标志位会被置位,语句15据此更新execute的值,Z标志位被置位的channel的execute被更新为0。这样就能满足我们前提到的,只有当execute等于0的channel才会执行指令。
 
当else语句执行完毕后,我们就退出了if-then-else语句的处理流程。
 
LOOP
 
在SIMD指令下的loop语句块实现,与if-then-else语句块的实现方法类似。同样的,在loop中也会因为各个channel的情况不同,可能会产生控制流程分歧。当控制流存在分歧时,同样要依据各个channel的控制条件决定是否执行对应的指令,处理完分歧后,每个channel再汇合到一起。
 
这里考虑一种一般的情况,示例代码如下:
 
Loop {
 
    if (…) {
        …
        Break;
    }
 
    … …
 
    if (…) {
        …
        Continue;
    }
 
    … …
}
 
如果没有遇到break、continue语句loop会一直循环下去。而break中断并结束循环,continue中断本次循环,继续下一次循环(与C语言的类似)。
从上面的示例代码可以看出,loop中会产生分歧主要是源自于其中的if-then-else语句。当各个channel在执行if-then-else语句出现分歧后,在执行loop中语句就channel的执行情况就不一样了。
例如,当两个channel出现执行if语句出现分歧,A满足if语句块执行条件,B不满足,在if语句块中有break语句,if语句按前文所述的if-then-else语句处理分歧,A执行,B不执行,执行完毕if语句块后,A不再执行loop中的指令,B继续执行loop中的语句,直到满足推出loop的条件,最终与A汇合。
 
具体的实现与if-then-else语句类似,使用一个变量execute来记录执行条件,当channel的execute等于0时表示,该channel需要执行当前指令;对于不需要执行当前指令的channel,execute的值等于下一个需要执行的语句块的入口地址。
 
伪代码:
LOOP
 
01: mov execute, 0
 
02: loop:
 
03: sub.sf tmp0, execute, @loop_block
04: mov.zs execute, 0
 
05: …
 
/* inside an if block */
06: mov.sf null, execute
07: mov.zs execute, jump_block       /* jump_block = @loop_block or @break_block */
08: sub.sf null, execute, jump_block
09: branch.all_zero_set jump_block
 
10: ...
 
11: mov.sf null, execute
12: sub.zc.sf null, execute, @loop_block
13: branch.any_zero_set @loop_block
14: loop_end:
 
15: break_block:
 
1)
进入loop语句,首先对各个channel的execute进行测试,等于0的channel可以执行loop语句块中的指令。
同样loop语句块中的指令依然需要通过在每条语句执行前添加“mov.sf null, execute”来设置Z标志位(赞不考虑优化),每条指令添加条件执行码。
语句1,对execute初始化。
语句3,对execute测试,在进入loop前如果execute等于@loop_block,表示该channel会遭loop中执行,Z标志位被置位,然后语句4将对应channel的execute更新为0,反之,保存execute原值不变(初次进入时execute保持为0值)。
这样loop中的指令语句就能根据execute的值,判断是否被执行。
 
2)
当遇到if语句时,if语句的处理与前文所述的不变,只是在进入if-then-else语句块前不再将各个channel的execute初始化为0。
 
3)
当if语句中存在break指令时,并能执行break指令,对于满足if语句块执行条件的channel,将在break执行完毕后,结束loop语句块的执行,这些channel的下一个执行语句块是@break_loop(即退出loop语句块的指令语句地址)。而不满足if语句块执行条件的channel将继续执行loop中的指令语句。
语句6、7首先完成对break的channel的execute进行更新,这些channel下一个要执行的语句块地址为@break_loop,这样在接下来的循环体中,如果没有发生共同跳转,那么剩下的loop语句块中的条件执行指令,均不能满足执行条件。
语句8、9是为应对当所有channel均执行了break指令的情况,这时所有channel同时结束loop语句块的执行。具体来看,首先对更新后的execute进行测试,语句8与jump_block(break_block)相减,其结果会影响Z标准位,如果所有channel均置位了Z标志位,语句9的跳转条件满足,所有channel将共同跳转到break_block(break_block),也就跳出了loop语句块。
break指令是loop的唯一出口,所以各个channel一定会从某个break语句结束循环(死循环除外)。
 
4)
当if语句中存在continue指令时,并且continue指令能被执行,对于满足if语句块执行条件的channel,在执行完continue指令后,中断本次循环的执行,重新跳转到loop的开始处@loop_block,继续执行下一次的循环。
与break的情况类似,同样可以通过伪代码的语句6-8来处理continue指令,语句6置位测试条件,语句7根据测试结果设置新的跳转地址 @loop_block,语句8、9同样会判断是否所有channel的执行情况相同,相同时时直接开始下一次循环。
当各个channel的情况不一样时,还会接着loop语句块往下执行,同样的执行了continue的channel的execute已被更新,且不为0,loop语句块中剩余的代码也不会被这些channel执行了。
在完成一次loop语句块中代码的执行后,后开始新的一次循环前(如1中所述),均要做条件测试。
 
执行完一次loop后,语句11根据execute当前的值对Z标志位进行更新,语句12是条件执行指令,并且满足执行条件的channel会更新Z标志位,而不满足语句12执行条件的channel的Z标志位保存会不变(处于Z标志位置位状态)。
满足Z标志位未被置位的channel,会将execute与@loop_block相减,并更新Z标志位,对于执行过continue的channel,Z标志位会被置位,语句13,branch的跳转条件是任意channel为Z标志位被置位,就会回到loop的开头处@loop_block,继而进行下一循环.
这里能满足语句13跳转条件的channel,来至两种情况,一种是在循环体中未执行过break和continue指令的channel,execute一直保持为0,在语句11中置位Z标志位,且语句12不会清除Z标志位;另一种是执行了continue指令的channel。
所以在执行下一次循环前,各个channel的execute的值可能是0或@loop_block,且一定有某个channel的值为其中之一。
重新开始循环后,会先按上述1中的方式更新execute,然后就是再一次的循环体代码执行了。
 
总结一下
1.控制语句块中,执行每条代码前必须做条件测试(不考虑优化),每条代码必须按条件执行。
2.进入控制语句块前需做语句块执行条件测试
3.退出控制语句前,需要更新各个channel的跳转地址
 
参考资料:
mesa:src/gallium/drivers/vc4/vc4_program.c

if-then-else、loop控制语句在SIMD指令下的后端指令生成实现--笔记的更多相关文章

  1. JSP中编译指令include与动作指令include的区别

    include指令是编译阶段的指令,即include所包含的文件的内容是编译的时候插入到JSP文件中,JSP引擎在判断JSP页面未被修改, 否则视为已被修改.由于被包含的文件是在编译时才插入的,因此如 ...

  2. 用ticons指令结合ImageMagickDisplay工具批量生成Android适应图片

    用ticons指令结合ImageMagickDisplay工具批量生成Android适应图片 ticons的用法可以百度 这里记录下具体的编译方法 在安装了ticons和ImageMagickDisp ...

  3. 使用B或BL跳转时,下一条指令的地址是这样计算的

    B跳转指令:它是个相对跳转指令,其机器码格式如下: [31:28]位是条件码:[27:24]位为“1010”(0xeaffffff)时,表示B跳转指令,为“1011”时,表示BL跳转指令:[23:0] ...

  4. jsp的常用指令有哪些(编译指令/动作指令整理)

    jsp的常用指令有哪些(编译指令/动作指令整理) JSP动作指令 JSP - JSP中的脚本.指令.动作和注释

  5. ARM指令集——条件执行、内存操作指令、跳转指令

    ARM 汇编指令条件执行 在ARM模式下,任何一条数据处理指令可以选择是否根据操作的结果来更新CPSR寄存器中的ALU状态标志位.在数据处理指令中使用S后缀来实现该功能. 不要在CMP,CMN,TST ...

  6. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十八║Vue基础: 指令(下)+计算属性+watch

    回顾 今天来晚辣,给公司做了一个小项目,一个瀑布流+动态视频控制的DEMO,有需要的可以联系我,公司的项目就不对外展示了(一个后端程序员真的要干前端了哈哈哈). 书接上文,昨天正式的开始了Vue的代码 ...

  7. 汇编 shr 逻辑右移指令,shl 逻辑左移指令,SAL 算术左移指令,SAR 算术右移指令

    知识点: shr 逻辑右移指令 shl 逻辑左移指令 一.SHL 逻辑左移指令测试 shr 逻辑右移指令 右移一位相当于整除2 shl 逻辑左移指令 左移一位相当于乘2 //很多时候会溢出 //& ...

  8. 使用B或BL跳转时,下一条指令的地址的计算

    .text .global _start 3_start: b step1 step1: ldr pc, =step2 step2: b step2 反汇编代码: : eaffffff b 0x4 : ...

  9. angular指令详解--自定义指令

    自定义指令 directive()这个方法是用来定义指令的: angular.module('myApp', []) .directive('myDirective', function ($time ...

随机推荐

  1. waeshall算法原理和实现

    传递闭包Warshall方法简要介绍 ① 在集合X上的二元关系R的传递闭包是包含R的X上的最小的传递关系.R的传递闭包在数字图像处理的图像和视觉基础.图的连通性描述等方面都是基本概念.一般用B表示定义 ...

  2. 看看poll 事件掩码 --- review代码时发现掩码不分的错误

    事件 描述 是否可作为输入(events) 是否可作为输出(revents) POLLIN 数据可读(包括普通数据&优先数据) 是 是 POLLOUT 数据可写(普通数据&优先数据) ...

  3. tcpack----- 2sack dack

    道当我们接收到ack的时候,我们会判断sack段,如果包含sack段的话,我们就要进行处理.这篇blog就主要来介绍内核如何处理sack段 sack dsack概念 TCP在一个RTO超时后会重传数据 ...

  4. Best Time to Buy and Sell Stock I II III IV

    一.Best Time to Buy and Sell Stock I Say you have an array for which the ith element is the price of ...

  5. Java初始化静态变量的时间顺序

    1. 开始吧! 今天,我们来探讨交流下静态变量初始化过程.Java虚拟机在类加载期间也同样遵循这个过程. 2. 初始化过程 在较高的层次上,JVM执行以下步骤: 首先,加载并链接类.然后,这个过程的& ...

  6. 头秃了,Spring Boot 自动配置了解一波~

    持续原创输出,点击上方蓝字关注我 目录 前言 源码版本 @SpringBootApplication干了什么? @EnableAutoConfiguration干了什么? 总结 前言 为什么Sprin ...

  7. Python_爬虫_百度图片

    百度图片有些有编码问题,暂时不能爬取,多试几个 #思路:抓取图片地址,根据地址转存图片(注意名称):难点:转码 # -*- coding:utf-8 -*- from urllib import re ...

  8. 这 12 款 IDEA 插件你用过几款?

    搞 Java开发用什么软件,当然是神器idea了,那么,idea的插件对于你来说就是必不可少的了,不仅可以提高自己的编码效率,还可以减轻工作时的枯燥烦闷.接下来就来说说,我平时敲代码用的什么插件吧. ...

  9. 基于Opencv识别,矫正二维码(C++)

    参考链接 [ 基于opencv 识别.定位二维码 (c++版) ](https://www.cnblogs.com/yuanchenhui/p/opencv_qr.html) OpenCV4.0.0二 ...

  10. 攻防世界app2 frida获取密钥

    环境准备 安装mumu模拟器 pip安装frida,这里到最后一步setup需要很长时间. 在frida github下载对应服务端. apk下载:https://adworld.xctf.org.c ...