科普LLVM中MSP430如何lower移位指令
来源:互联网 发布:冰锐和锐澳的区别 知乎 编辑:程序博客网 时间:2024/05/21 22:59
问题背景
最近在做LLVM项目的时候,碰到了一个看起来不是问题的问题,就是如何对移位指令进行lower,在LLVMIR中,移位指令使用一个节点进行表示的,表示如下
(shl a, b)
其中,假设a就是源操作数,b是移位次数,shl为逻辑左移,可是MSP430芯片里面只有一次移动一位的移位指令,如下
(MSP430shl a)
表示将源操作数a向左移动一位,那么下一步,顺理成章地就会想到,如果每次只移动一位,那么肯定要使用循环进行实现啊,没错,可是问题就在这里,如何用循环实现。
编译器背景知识
基本块
所谓的基本块,比如在C语言中一段代码要么全部执行,要么全部不执行,比如如下代码
if(a == 1) { b = b + 1; for(int c = 0; c < 1; c ++) { b = b + c; } a = b;}
这是我随便写的一段代码,里面就包含三个基本块,分别是
b = b + 1
for(int c = 0; c < 1; c ++) { b = b + c;}
a = b;
DAG
DAG就是有向无环图,每个基本块在进行分析的时候都是变成一个有向无环图进行处理的,是一一对应关系,简单的例子就是数据结构里面的有向无环图的举例,那就是一个简单的语法树。
解决问题
在上一个章节说过,一个基本块是对应一个DAG的,然而如果你要处理第一个部分所说的移位指令,那么你就要生成一个循环,一个循环。。一个循环。。。。
问题来了,循环内部是一个基本块,也就是说,循环的出现使得整个基本块变成了三个,从而基本块和DAG的关系不满足一对一,而是三对一了,这显然是不能够处理的,但是,办法总是有的,办法就是使用伪指令匹配。
- 如果碰到了移位指令,比如上面的逻辑左移,那么就只是简单的替换一下名称,源码的出现位置如下:
SDValue MSP430TargetLowering::LowerShifts(SDValue Op, SelectionDAG &DAG) const { unsigned Opc = Op.getOpcode(); SDNode* N = Op.getNode(); EVT VT = Op.getValueType(); SDLoc dl(N); // Expand non-constant shifts to loops: if (!isa<ConstantSDNode>(N->getOperand(1))) switch (Opc) { default: llvm_unreachable("Invalid shift opcode!"); case ISD::SHL: return DAG.getNode(MSP430ISD::SHL, dl, VT, N->getOperand(0), N->getOperand(1)); case ISD::SRA: return DAG.getNode(MSP430ISD::SRA, dl, VT, N->getOperand(0), N->getOperand(1)); case ISD::SRL: return DAG.getNode(MSP430ISD::SRL, dl, VT, N->getOperand(0), N->getOperand(1)); } uint64_t ShiftAmount = cast<ConstantSDNode>(N->getOperand(1))->getZExtValue(); // Expand the stuff into sequence of shifts. // FIXME: for some shift amounts this might be done better! // E.g.: foo >> (8 + N) => sxt(swpb(foo)) >> N SDValue Victim = N->getOperand(0); if (Opc == ISD::SRL && ShiftAmount) { // Emit a special goodness here: // srl A, 1 => clrc; rrc A Victim = DAG.getNode(MSP430ISD::RRC, dl, VT, Victim); ShiftAmount -= 1; } while (ShiftAmount--) Victim = DAG.getNode((Opc == ISD::SHL ? MSP430ISD::RLA : MSP430ISD::RRA), dl, VT, Victim); return Victim;}
可以看到就是switch中的第一个case
case ISD::SHL: return DAG.getNode(MSP430ISD::SHL, dl, VT, N->getOperand(0), N->getOperand(1));
变量N
就是原始的移位操作节点,操作符是ISD::SHL
,这里取第0个操作数N->getOperand(0)
和第1个操作数N->getOperand(1)
,使用MSP430ISD::SHL
进行节点的构造,可以看出,这里仅仅只是替换了一个名字而已,那么MSP430ISD::SHL
这个节点在哪里定义的呢?答案是在td文件里面定义的,存在td文件MSP430InstrInfo.td
,定义处如下
def MSP430shl : SDNode<"MSP430ISD::SHL", SDT_MSP430Shift, []>;
- 此时我们已经用
MSP430ISD::SHL
替换ISD::SHL
了,对于这条移位指令来说,整个lower阶段就已经结束了,接下来进入指令选择阶段,要开始进行DAG Covering了,同样,在td文件中,我们可以发现如下的匹配规则
def Shl8 : Pseudo<(outs GR8:$dst), (ins GR8:$src, GR8:$cnt), "# Shl8 PSEUDO", [(set GR8:$dst, (MSP430shl GR8:$src, GR8:$cnt))]>;
这条匹配规则的含义就是用MSP430shl
操作对src
和cnt
两个操作数进行运算,运算的结果放在dst
寄存器里面,ps.这个格式是Lisp风格的,很适合表示一个图结构,然后整个操作匹配成了一条伪指令,至于什么是伪指令,其实有一种很好理解的方式就是,伪指令也是一种算数运算,这种运算过程可能有很多步,但是对于之后的指令,如果要使用这条伪指令的结果,尽管使用,不用管这个值是怎么求出来的,有一种面向对象的特性“封装”的既视感。
- 用伪指令进行指令选择之后,整个DAG图变成了一个指令序列,相当于是
DAG -> List<Instruction>
,此时还有最后一个处理步骤就是伪指令扩展,源码中有两个位置处理伪指令扩展,一个是td文件里面
let usesCustomInserter = 1 in { def Select8 : Pseudo<(outs GR8:$dst), (ins GR8:$src, GR8:$src2, i8imm:$cc), "# Select8 PSEUDO", [(set GR8:$dst, (MSP430selectcc GR8:$src, GR8:$src2, imm:$cc))]>; def Select16 : Pseudo<(outs GR16:$dst), (ins GR16:$src, GR16:$src2, i8imm:$cc), "# Select16 PSEUDO", [(set GR16:$dst, (MSP430selectcc GR16:$src, GR16:$src2, imm:$cc))]>; let Defs = [SR] in { def Shl8 : Pseudo<(outs GR8:$dst), (ins GR8:$src, GR8:$cnt), "# Shl8 PSEUDO", [(set GR8:$dst, (MSP430shl GR8:$src, GR8:$cnt))]>; def Shl16 : Pseudo<(outs GR16:$dst), (ins GR16:$src, GR8:$cnt), "# Shl16 PSEUDO", [(set GR16:$dst, (MSP430shl GR16:$src, GR8:$cnt))]>; def Sra8 : Pseudo<(outs GR8:$dst), (ins GR8:$src, GR8:$cnt), "# Sra8 PSEUDO", [(set GR8:$dst, (MSP430sra GR8:$src, GR8:$cnt))]>; def Sra16 : Pseudo<(outs GR16:$dst), (ins GR16:$src, GR8:$cnt), "# Sra16 PSEUDO", [(set GR16:$dst, (MSP430sra GR16:$src, GR8:$cnt))]>; def Srl8 : Pseudo<(outs GR8:$dst), (ins GR8:$src, GR8:$cnt), "# Srl8 PSEUDO", [(set GR8:$dst, (MSP430srl GR8:$src, GR8:$cnt))]>; def Srl16 : Pseudo<(outs GR16:$dst), (ins GR16:$src, GR8:$cnt), "# Srl16 PSEUDO", [(set GR16:$dst, (MSP430srl GR16:$src, GR8:$cnt))]>; }}
内容如上,对于要进行伪指令扩展的指令,需要声明usesCustomInserter = 1
。C++中的源码如下:
MachineBasicBlock*MSP430TargetLowering::EmitInstrWithCustomInserter(MachineInstr *MI, MachineBasicBlock *BB) const { unsigned Opc = MI->getOpcode(); if (Opc == MSP430::Shl8 || Opc == MSP430::Shl16 || Opc == MSP430::Sra8 || Opc == MSP430::Sra16 || Opc == MSP430::Srl8 || Opc == MSP430::Srl16) return EmitShiftInstr(MI, BB); ......}
其中,这里的
MachineBasicBlock*MSP430TargetLowering::EmitInstrWithCustomInserter(MachineInstr *MI, MachineBasicBlock *BB) const;
只一个纯虚函数,需要由子类进行实现,这里就由MSP430平台进行了实现,可以看到,在函数的开头,就进行了判断,如果是移位指令就转到EmitShiftInstr
函数体内进行处理。EmitShiftInstr
函数我就补贴代码了,下面就直接用图和代码进行说明,对照图,代码就很好看懂。
存在伪指令节点
(MSP430shl GR8:$src, GR8:$cnt)
最终生成的汇编码自己手写如下
////// // Previous Part : Part Aloop: shl src // 将src左移一位 dec cnt // 移位次数减一 eq cnt, 0 // 如果移位次数为0,那么就跳过下一条语句,反之不跳过 jmp loop // 跳转指令,跳转到loop继续执行///// // Remain Part : Part B
反映在实际操作中就是手撸DAG图,对其进行改造
伪指令节点DAG:
最终变换完成的DAG图如下:
其中,中间的大节点中的指令是硬编码的,然后再在中间的大节点的开头插入phi函数,完成移位指令的lower。
结语
细心的你们会发现为什么中间的大节点和后面的jmp节点前后为什么会有留白区域,很简单,这只是贫僧画图水平不高,做了两个标记,不知道怎么去掉而已,ps,画图工具是Graphviz。
- 科普LLVM中MSP430如何lower移位指令
- 移位指令
- 移位指令
- 移位指令
- LLVM 指令统计
- llvm生成rdrand指令
- arm指令移位指令
- VB中位操作运算函数【移位指令】
- LLVM IR指令的抽象
- 逻辑运算指令和移位指令
- 逻辑运算指令和移位指令
- ARM指令集--移位指令
- 移位和循环指令
- 汇编语言---移位指令
- 8086移位指令
- LLVM/GCC中如何使用Intel格式的汇编
- LLVM/GCC中如何使用Intel格式的汇编
- LLVM--如何在代码中调用遍(Pass)
- ARM编程的编程模式和一些概念理解的地方
- xml-xml与java对象之间的转换(jaxb)
- 在 Windows 上简单的使用 GitHub
- 决策树(ID3)c++实现(未剪枝)
- 总是这么奇怪
- 科普LLVM中MSP430如何lower移位指令
- ORM与JPA及Hibernate三者之间的关系
- 数据结构 队列的实现
- 验证子串
- ResourceManager(一)—— ArchiveManager
- 万商联店铺会员管理系统v3.8破解带收银系统
- LinuxStudyNote(42)-RPM包安装、更新、卸载命令、包名和包全名的概念
- [线性规划 费用流]Codeforces Gym101190D.Delight for a cat
- Java I/O Reader及其子类源码解析