在llvm中完成if else语句的编译

本文介绍了如何使用LLVM将C语言中的if else语句编译为新的DSP芯片指令集。主要涉及添加比较和跳转指令,处理br_cc节点,以及解决BasicBlock识别问题。

说明:我们开发了一款新的DSP芯片,定义了一种新的指令集。现在使用llvm来将c代码编译成我们新定义的汇编指令集。本文是其中对c语言总if else语句的处理。

首先看一段c语言程序:

//c代码
void test_main()
{
    int a = 5;
    int b = 10;
    if(a == 5)
        b = 1;
    else
        b = 0;
}

要想编译这段语句,重点有两个,一个是条件语句的处理,第二个就是跳转。
我们首先看一下llvm把这段语句处理成的IR是什么形式:

//经llvm处理后的IR形式
  %cmp = icmp eq i32 %0, 5
  br i1 %cmp, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  store i32 1, i32* %b, align 4
  br label %if.end

if.else:                                          ; preds = %entry
  store i32 0, i32* %b, align 4
  br label %if.end

if.end:                                           ; preds = %if.else, %if.then
  ret void
}

忽略次要矛盾,主要的语句就是两句:

//IR中的关键语句
 %cmp = icmp eq i32 %0, 5
  br i1 %cmp, label %if.then, label %if.else

一句是比较指令,一句是跳转指令。
根据我们的汇编指令集,我们期望把它处理成这个样子:

//期望最终处理成的形式
    eq  $GR2, $GR3, $GR2
    jnc $BB0_2
    jmp $BB0_1
$BB0_1:                                 # %if.then
    movigl  $GR2,0
    movigh  $GR2,0
    store32 $GR2, $GR30, 0
    jmp $BB0_3
$BB0_2:                                 # %if.else
    movigl  $GR2,0
    movigh  $GR2,0
    store32 $GR2, $GR30, 0
$BB0_3:                                 # %if.end
    addi    $GR30, $GR30, 8
    ret 

我们做的工作是:

1、添加指令。

我们首先把比较指令EQ和跳转指令JNC添加上:
在llvm-3.5.0.src\lib\Target\DSP\DSPInstrFormats.td文件中添加这种模板:

/*================================================
typecode|opcode|rd|rs|rt|inner_op<8>
=================================================*/
class R3<bits<3>typecode, bits<3>opcode, bits<8> inner_op,dag outs, dag ins, string asmstr, list<dag> pattern, InstrItinClass itin>
   :InstDSP<outs, ins, asmstr, pattern, itin>{
  bits<6> rd;
  bits<6> rs;
  bits<6> rt;
  let Inst{25-20}=rd;
  let Inst{19-14}=rs;
  let Inst{13-8}=rt;
  let Inst{7-0}=inner_op;
  }

在llvm-3.5.0.src\lib\Target\DSP\DSPInstrInfo.td文件中添加如下代码进行指令描述:

class CBranch<bits<3> typeop, bits<3> op, bits<5> inner_op, string instr_asm>:
R0<typeop,op,inner_op, (outs), (ins CPURegs:$rt, brtarget:$addr),
!strconcat(instr_asm, "\t$addr"), [], IIBranch> {
let isBranch = 1;
let isTerminator = 1;
let isBarrier = 1;
let hasDelaySlot = 1;
}
class SetCC_R<bits<3> typeop,bits<3> op, bits<8> inner_op, string instr_asm, PatFrag cond_op , RegisterClass RC>
:R3<typeop,op,inner_op,(outs GPROut:$ra), (ins RC:$rb, RC:$rt),
!strconcat(instr_asm, "\t$ra, $rb, $rt"),
[(set GPROut:$ra, (cond_op RC:$rb, RC:$rt))],IIAlu>{
let isCommutable = 1;
}
def JNC : CBranch<0,0,0b01001, "jnc">;
def EQ   :SetCC_R<0,4,0b000000000,"eq",seteq,CPURegs>;
//添加pat,是完成一种替换的功能
// brcond for cmp instruction
multiclass BrcondPatsCmp<RegisterClass RC, Instruction JNEOp, 
Instruction CMPOp, Register ZEROReg> {

def : Pat<(brcond (i32 (setne RC:$lhs, RC:$rhs)), bb:$dst),
          (JNEOp (CMPOp RC:$lhs, RC:$rhs), bb:$dst)>;
def : Pat<(brcond (i32 (setune RC:$lhs, RC:$rhs)), bb:$dst),
          (JNEOp (CMPOp RC:$lhs, RC:$rhs), bb:$dst)>;
def : Pat<(brcond RC:$cond, bb:$dst),
          (JNEOp (CMPOp RC:$cond, ZEROReg), bb:$dst)>;
}
defm : BrcondPatsCmp<CPURegs, JNC, EQ, ZERO>;

2、对br_cc节点进行处理

在使用llc时加入-view-isel-dags就可以调用gvedit.exe来查看dag转换图啦。
这里写图片描述
在文件llvm-3.5.0.src\lib\Target\DSP\DSPISelLowering.cpp中

DSPTargetLowering::DSPTargetLowering(DSPTargetMachine &TM, const DSPSubtarget &STI)
:TargetLowering(TM, new DSPTargetObjectFile()), Subtarget(STI){
    ...
    setOperationAction(ISD::BR_CC, MVT::i32, Expand);//将BR_CC节点进行Expand操作
}

这样之后会发现还有问题,BasicBlock不识别。。。
接下来修改llvm-3.5.0.src\lib\Target\DSP\DSPMCInstLower.cpp

MCOperand DSPMCInstLower::LowerSymbolOperand(const MachineOperand &MO, MachineOperandType MOTy, unsigned Offset) const {
    MCSymbolRefExpr::VariantKind Kind;
    const MCSymbol *Symbol;
    switch (MO.getTargetFlags())
    {
    default:llvm_unreachable("Invalid target flag!");
        break;
    case DSPII::MO_NO_FLAG: Kind = MCSymbolRefExpr::VK_None; break;
    case DSPII::MO_ABS_HI: Kind = MCSymbolRefExpr::VK_DSP_ABS_HI; break;
    case DSPII::MO_ABS_LO: Kind = MCSymbolRefExpr::VK_DSP_ABS_LO; break;
    case DSPII::MO_GPREL: Kind = MCSymbolRefExpr::VK_DSP_GPREL; break;
    case DSPII::MO_GOT: Kind = MCSymbolRefExpr::VK_DSP_GOT; break;
    }
    switch (MOTy){
    case MachineOperand::MO_GlobalAddress:
        Symbol = AsmPrinter.getSymbol(MO.getGlobal());
        break;
    case MachineOperand::MO_ConstantPoolIndex:
        Symbol = AsmPrinter.GetCPISymbol(MO.getIndex());
        break;
    case MachineOperand::MO_BlockAddress:
        Symbol = AsmPrinter.GetBlockAddressSymbol(MO.getBlockAddress());
        Offset += MO.getOffset();
        break;
    case MachineOperand::MO_JumpTableIndex:
        Symbol = AsmPrinter.GetJTISymbol(MO.getIndex());
        break;
    case MachineOperand::MO_MachineBasicBlock://这一步处理BasicBlock类型
        Symbol = MO.getMBB()->getSymbol();
        break;
    default:
        llvm_unreachable("<unknown operand type>"); break;
    }
    const MCSymbolRefExpr *MCSym = MCSymbolRefExpr::Create(Symbol, Kind, *Ctx);

    if (!Offset)
        return MCOperand::CreateExpr(MCSym);
    assert(Offset > 0);
    const MCConstantExpr *OffsetExpr = MCConstantExpr::Create(Offset, *Ctx);
    const MCBinaryExpr *AddExpr = MCBinaryExpr::CreateAdd(MCSym, OffsetExpr, *Ctx);
    return MCOperand::CreateExpr(AddExpr);
}

ok,完工,能够生成我们期望的汇编指令了。
总结:
需要做的工作有:
1、添加指令:
llvm-3.5.0.src\lib\Target\DSP\DSPInstrFormats.td
llvm-3.5.0.src\lib\Target\DSP\DSPInstrInfo.td
2、处理节点:
llvm-3.5.0.src\lib\Target\DSP\DSPISelLowering.cpp
3、添加BasicBlock操作数类型:
llvm-3.5.0.src\lib\Target\DSP\DSPMCInstLower.cpp

<think>我们正在讨论如何将if-else和while语句转换为三地址代码,并正确实现goto跳转到正确的标号。 三地址代码是一种中间表示形式,每条指令最多包含三个操作数。对于控制流语句,我们需要使用条件跳转和无条件跳转指令。 1. if-else语句的三地址代码实现: 基本结构: if (condition) then S1 else S2 转换步骤: - 为条件表达式生成代码,将结果存入一个临时变量(如果条件不是简单变量)。 - 生成条件跳转指令:如果条件为假,则跳转到else部分。 - 生成then部分(S1)的代码,然后生成一个无条件跳转指令跳过else部分。 - 生成else部分(S2)的代码。 - 需要正确设置标签(label)来标记跳转目标。 示例: 考虑:if (a < b) then x = y else x = z 三地址代码: t1 = a < b // 计算条件 if_false t1 goto L1 // 如果条件为假,跳转到L1(else部分) x = y // then部分 goto L2 // 跳过else部分 L1: x = z // else部分 L2: ... // 后续代码 2. while语句的三地址代码实现: while (condition) S 转换步骤: - 在循环开始前设置一个标签(比如L_loop)来标记循环条件检查的位置。 - 生成条件表达式的代码,将结果存入临时变量。 - 生成条件跳转指令:如果条件为假,则跳转到循环结束后的标签(L_exit)。 - 生成循环体S的代码。 - 在循环体后生成一个无条件跳转指令,跳转回条件检查标签(L_loop)。 - 设置循环结束的标签(L_exit)。 示例: while (a < b) { a = a + 1; } 三地址代码: L_loop: t1 = a < b if_false t1 goto L_exit // 条件为假则退出循环 a = a + 1 goto L_loop L_exit: ... 3. 如何goto到正确的标号: 在生成代码的过程中,我们需要维护一个标签的管理机制。通常,编译器或中间代码生成器会使用一个计数器来生成唯一的标签(如L1, L2, ...)。当遇到控制流语句时,生成新的标签,并在对应的位置插入标签定义。跳转指令则引用这些标签。 例如,对于if-else语句,我们生成两个标签:一个用于else部分(L1),一个用于跳出else部分(L2)。在生成条件跳转时,跳转到L1;在then部分结束后,生成一个跳转到L2的指令;然后在L1处生成else部分的代码,最后在L2处继续。 4. 编程实现时,通常使用递归下降或其他语法分析方法,在分析到控制结构时,生成相应的标签和跳转指令。 下面是一个简化的伪代码示例,展示如何为if-else生成三地址代码: function generateIfElse(condition, thenStmt, elseStmt): label1 = newLabel() // 用于else部分 label2 = newLabel() // 用于跳出else部分 // 生成条件表达式代码 temp = generateExpression(condition) emit("if_false " + temp + " goto " + label1) // 生成then部分 generateStatement(thenStmt) emit("goto " + label2) // 生成else部分标签和代码 emit(label1 + ":") generateStatement(elseStmt) // 生成结束标签 emit(label2 + ":") 类似地,while循环的生成: function generateWhile(condition, bodyStmt): startLabel = newLabel() exitLabel = newLabel() emit(startLabel + ":") temp = generateExpression(condition) emit("if_false " + temp + " goto " + exitLabel) generateStatement(bodyStmt) emit("goto " + startLabel) emit(exitLabel + ":") 注意:在实际实现中,需要处理嵌套的控制结构,确保标签的唯一性和正确跳转。 引用说明:在编译原理中,中间代码生成是编译器前端的重要步骤,三地址代码是其中一种常用的中间表示形式[^1]。 </think> ### if-else和while语句的三地址代码实现及跳转控制 #### 一、三地址代码基本概念 三地址代码(Three-Address Code)是编译器常用的中间表示形式,每条指令最多包含三个操作数,基本格式为: ```c x = y op z ``` 其中`op`可以是算术运算符、比较运算符或跳转指令[^1]。 #### 二、if-else语句实现 **实现步骤**: 1. 生成条件表达式的比较代码 2. 使用条件跳转指令(如`if_false`) 3. 为真/假分支设置标签 4. 通过`goto`实现控制流跳转 **示例代码**: ```c // 源代码:if (a > b) x=1; else x=2; t1 = a > b // 条件计算 if_false t1 goto L1 // 条件跳转 x = 1 // true分支 goto L2 // 跳过else块 L1: // else标签 x = 2 // false分支 L2: // 后续代码 ``` **关键点**: 1. `if_false`指令在条件为假时跳转到`L1` 2. `goto L2`确保执行完true分支后跳过else分支 3. 标签`L1`和`L2`标记不同代码块的入口 #### 三、while语句实现 **实现步骤**: 1. 在循环开始处设置入口标签 2. 生成循环条件判断 3. 条件失败时跳转到退出标签 4. 循环体末尾添加跳回条件判断的指令 **示例代码**: ```c // 源代码:while (i < 10) i=i+1; L_loop: // 循环入口 t1 = i < 10 // 条件计算 if_false t1 goto L_exit // 退出循环 i = i + 1 // 循环体 goto L_loop // 跳回循环开始 L_exit: // 循环结束 ``` **关键点**: 1. `L_loop`是循环的重新入口点 2. `if_false`在条件不满足时跳转到`L_exit` 3. 循环体末尾的`goto`实现循环重复 #### 四、goto跳转实现原理 1. **标签管理**: - 编译器为每个跳转目标生成唯一标签(如`L1, L2,...`) - 建立标签与内存地址的映射表 2. **跳转指令生成**: ```c // 生成goto指令示例 emit("goto " + label_name); // 生成条件跳转示例 emit("if_false " + condition_var + " goto " + target_label); ``` 3. **后端处理**: - 汇编阶段将标签转换为实际内存地址 - 机器码使用相对/绝对地址跳转指令 - 例如x86的`JMP`指令对应高级`goto` #### 五、实际编译器实现技巧 1. **嵌套结构处理**: - 使用栈管理嵌套的标签作用域 - 为每个控制结构生成唯一标签前缀 2. **短路求值优化**: ```c // if (a && b) 的短路实现 if_false a goto L_false if_false b goto L_false // true分支代码... goto L_exit L_false: // false分支代码... L_exit: ``` 3. **中间代码优化**: - 删除冗余跳转(连续goto合并) - 无用标签消除 - 基本块重组 > 现代编译器(如GCC/LLVM)通过控制流图(CFG)管理三地址代码的跳转关系,每个基本块以跳转指令结束,块间通过有向边连接[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值