指令选择器调查
Posted wuhui_gdnt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了指令选择器调查相关的知识,希望对你有一定的参考价值。
5. 基于图的做法
依赖图覆盖的指令选择器是目前能得到的最强大的代码生成器。通过允许输入及模式具有任意的图形状,指令选择器能够接受整个函数为输入——这被称为全局指令选择——有可能处理各种机器指令,包括硬件循环及SIMD指令。最重要的,与局限在单个基本块的基于DAG覆盖的技术相比,全局指令选择器可以在多个块间自由地移动及覆盖节点。这增加了应用复杂模式的机会,这可能导致性能提升及减少功耗。
因为大多数图覆盖方法通过应用某些通用的子图同构算法来处理模式匹配问题,我们将从简单地看几个这样的算法开始。
图5.1:图覆盖问题的一个例子。模式实例由虚线及包含了该模式匹配节点的阴影面积表示。
5.1. 子图同构算法
子图同构问题是检测是否可以转动、扭转或镜像任意一个图Ga,使得它成为另一个图Gb的一个子图。在这样的情形里,称Ga是Gb的一个子图同构,它已知是待定的一个NP完全问题【55】。因为在许多其他领域都能发现子图同构,大量的研究一直致力于这个问题。在本节,我们将简单看一下两个这样的图匹配算法。
一个早期著名的子图同构算法由Ullmann开发【226】。在1976年的论文里,Ullmann将确定一个图Ga = (Va, Ea)是否子图同构于另一个图Gb = (Vb,Eb) 的问题描述为找出一个布尔|Va| × |Vb|矩阵Mꞌ,使得
C = Mꞌ (MꞌB)T
∀i, j, 1 ≤ i ≤ |Va|, 1 ≤ j ≤ |Vb|: (aij= 1) ⇒ (cij = 1)
成立,其中A与B分别是Ga与Gb的相邻矩阵。在Mꞌ这样的实例里,每行只包含一个1,而每列最多包含一个1。通过初始化每个单元mꞌij为1,然后裁剪掉1直到找到一个解答来蛮力查找Mꞌ。为了减少查找空间,Ullmann开发了一个过程在开始查找前消除某些1。不过,该算法的最坏情况时间复杂度是O(n!n2)(根据【57】),并且使用相邻矩阵排斥了对可能出现在程序表示中多边界图的处理。
最近,Cordella等【57】提出了一个称为VF2的算法,它已经被用在几个基于DAG及图覆盖的指令选择器。特别的,它已经用于辅助指令集提取(instruction set extraction,ISE;参考【10,50,75】为例)。大致上,通过一个迭代的方式把一个新对加入集合,VF2算法递归地构造“(n,m)对”的一个映射集,其中n ∈ Ga而m ∈ Gb。该算法的核心是一个规则,它禁止添加阻止形成从Ga到Gb一个有效子图同构映射的对。在最坏情形下,该算法具有O(n!n)时间复杂度,但报告表示它已经被成功地应用于包含超过一千个节点的图,因为在最好的情形下,它具有多项式时间复杂度。另外,VF2算法可以处理多边界图(multi-edge graphs)。
在本调查进行中找到的、别的关于子图同构的论文包括:Guo等【119】,Krissinel与Henrick【152】,Sorlin与Solnon【213】(为查找子图同构展示了一个全局限制),Gallagher【102】,Fan等【82】,Fan等【83】,及Hino等【129】。
5.2. 第一批方法
在1994年,Liem等【165】在一篇被大量引用的论文里展示了一个在CDFG(控制及数据流图)上执行模式匹配与选择的做法。实现在一个称为CodeSyn的代码生成系统里——它又是一个称为FlexWare的嵌入式开发环境的一部分——这是第一个已知的技术,执行全局指令选择,同时处理数据流、控制流模式以及控制-数据流混合模式。虽然论文的做法局限于树模式,Liem等宣称它可以容易地扩展到DAG模式。他们的匹配算法在最坏情形下使用O(n2)时间,类似于Weingart的树模式匹配程序(参考3.1节),模式构成了单个类似树的结构。从该结构的根节点出发,匹配程序比较CDFG中的当前节点,在分支处深入。模式以这样一种方式排列,如果在该结构中更高处的匹配失败,可以删除整棵子树,因此减少了匹配时间。选择通过动态规划来完成。不过,Liem等没有提供一个方法来自动地排序模式以构建树,模式是否可以扩展到多个无关的图还存在疑问。
同一年,Van Praet等【227,228】开发了另一个方法,它实现在CHESS中,一个作为欧洲项目部分的面向DSP的编译器。在这个做法里,模式被自动从一个以nML编写的处理器描述导出——nML是一个由Fauth等构想的一个领域特定语言【85】——它指定了一幅由两部分构成,代表了目标处理器数据路径的图。在一个输入CDFG的代码生成过程里,处理器描述中的单元被分为包。然后这些包被转换为对应的模式(它可以是任意图形),接着匹配与选择模式。如果能产生一个更好的结果,模式允许重叠。论文没有讨论匹配过程,但声明使用了一个分支界定算法(branch-and-bound algorithm)完成选择。这个算法也假定所有的模式具有非负、不变的开销,这允许受处理的图被分解为更小、更简单,可以被单独覆盖的实例。不过,这阻止了对可以并行执行机器指令的利用,因为这样指令的选择不需要承担额外的开销。另外,不清楚在他们的方法中复杂处理器是否可以被正确地构建。
5.3. 单边及双边覆盖技术
解决模式选择问题的另一个方式是把它转换为一个等价的单边或双边覆盖问题。虽然基于双边覆盖的技术先出现,我们将先讨论单边覆盖,因为双边覆盖是单边覆盖的一个扩展。
5.3.1. 单边覆盖
Clark等【51】发表了一篇论文描述了用于非循环计算加速器(acycliccomputation accelerators)的代码生成方法。一个程序的部分可以由定制的加速器实现并执行,因此得到性能提升,在这个层面上,这样的加速器是可编程的。算法首先在输入的DAG上使用一个修改的Ullmann算法修剪不是子图同构的子图,枚举所有可能的子图(即模式)。一旦枚举了所有的模式,模式选择任务就被阐述为一个单边覆盖问题。这个问题包含了一个布尔矩阵M,其中列代表模式实例,行代表被覆盖的图形节点。因此,如果mij = 1,那节点i为模式实例j覆盖。这也显示在了图5.2里。因此,目标是选择列,使得每个节点以最小的代价被至少一个模式覆盖(该算法阻止重叠,Clark等辩论这减少了生成代码的功耗)。换而言之,如果N是输入图中的节点集合,P是模式实例的集合,那么
必须成立,同时最小化选中模式的总开销。作者在论文中(即【58,113】)提到他们考虑后认为,单边覆盖是解决这些问题已有、高效的启发式,他们的实验显示该算法显示出了线性的运行时间复杂度。这个方法后来被Hormati等【132】扩展,以减少加速器设计的互联与数据中枢的延迟。
图5.2:单边覆盖的例子。矩阵中以星号标记的1代表潜在、但没选中的覆盖,没有标记的1表示最优、选中的覆盖。假定所有的模式都有相同的单位开销。
单边覆盖也被Martin等【168】使用,但不像Clark等,他们把这个问题阐述为一个约束编程模型(CP)。然后,合并了指令调度,寻找这个CP模型的最优解决。这个做法也被Floch等【91】扩展来适应VLIW架构。
5.3.2. 双边覆盖
单边覆盖问题的矩阵可以重写为一个包含非互补析取的合取(conjunctions of uncomplemented disjunctions)的布尔公式。我们将每个析取记为一个子句(clause)。作为一个例子,图5.2(b)中的布尔矩阵可以重写为
f = (p1 + p2)(p2+ p3)(p3 + p4)(p4 + p5)p6
因此目标是以最小开销满足f。不过,单边覆盖的问题是,它不能捕捉所有指令选择所必须的约束。大多数模式假定输入具有某个类型或位于一个特定的数据位置。在语法里,这通过非终结符来表示。使用相同的例子,让我们假设节点n1可以被多个单节点模式p1,pꞌ1,及pꞌꞌ1覆盖。让我们进一步假设模式p3,如果选中,要求节点n1必须被模式p1覆盖,且仅为该模式覆盖。这样的约束,对指令选择来说是常见的,不能被表示为一个单边覆盖问题的部分。这个缺点通过双边覆盖来解决。不像单边覆盖,双边覆盖允许析取包含非互补及互补的字面(即 )。现在我们刚讨论的约束可以被表达为 + p1,即如果我们选择p3,那么我们必须也选择p1,以使该子句为真。因此这样的子句被称为暗示子句(implication clauses),因为它等同于p3 ⇒ p1。
通过使用双边覆盖解决集成电路合成(VLSI synthesis)的DAG覆盖,Rudell【197】[1]引领了双边覆盖的应用。它也被Servít与Yi【206】应用来解决或多或少类似的技术变换(technology mapping)问题。
这个做法后来被Liao等【163,164】修改来处理指令选择。在他们分别发表在1995与1998年的论文里,Liao等描述了目标是一个寄存器的机器并优化代码大小的方法。为了控制复杂度,首先通过忽略数据传输及寄存器溅出开销来解决模式选择问题。在选择了复杂模式后,被覆盖节点缩合为单个节点,构建第二个双边覆盖问题以最小化数据传输的开销。虽然这两个问题可以被同时解决,Liao等选择不这样做,因为需要的子句数目会变得极大。
最近,Cong等【53】也把双边覆盖应用于指令选择,作为可配置处理器架构应用程序特定指令生成的一部分。不过,作者仅利用了之前的研究,而没有贡献将进一步推进指令选择的新知识。
虽然上面讨论的做法将自身局限于DAG,模式选择的双边覆盖思想可以扩展到任意图形模式。这,连同存在许多精确及近似解决双边覆盖问题的研究这个事实,使得它成为未来指令选择研究中一个有前途的候选。
1.2. 基于PBQP的做法
在2003年,Eckstein等【73】开发了第一个直接在SSA图上工作的技术。SSA代表静态、单赋值(static, single assignment),是一个程序表示形式,其中每个变量或临时变量仅定义一次。效果就是每个变量的生命周期是连续的,这简化了许多优化过程。因此,基于SSA的IR为许多现代编译器所使用,包括LLVM与gcc。关于SSA的更多信息,建议读者参考编译器教材【8】或【56】。
Eckstein等认识到在专用的DSP上执行定点算术运算的情形下,将指令选择限定在基本块会导致次优的代码。例如,在图5.3(a)中,我们看到一小段定点值乘法的代码。对于定点乘法,一个公共的乘法特性是,结果向左移一位。因此,为了有效地利用这样的乘法单元,在行4上的变量s中的结果在循环里必须维持左偏差,仅在函数返回前移回正常的模式。这意味着s的值在函数的不同点必须是不同的形式。不过,在限制在基本块的指令选择器中,这样的信息很难传播,因为s的定义与使用发生在不同的树中(参考图5.3的(b))。