【问题标题】:Redundant basic blocks in llvm IRllvm IR 中的冗余基本块
【发布时间】:2018-11-12 03:55:20
【问题描述】:

我只是在玩一个程序并在 llvm 中观察它的 IR,我注意到某些对我来说没有意义的基本块。 我的代码:

void proc()
{
    int i, j, k, m, n, l;
    k = 119;
    for (i = 20; i < 200; i++)
    {
        for (j = 13; j < 130; j++)
        {
                l = 80;
        }
    }
}

对应的IR:

 store i32 119, i32* %3, align 4
  store i32 20, i32* %1, align 4
  br label %7

; <label>:7:                                      ; preds = %19, %0
  %8 = load i32, i32* %1, align 4
  %9 = icmp slt i32 %8, 200
  br i1 %9, label %10, label %22

; <label>:10:                                     ; preds = %7
  store i32 13, i32* %2, align 4
  br label %11

; <label>:11:                                     ; preds = %15, %10
  %12 = load i32, i32* %2, align 4
  %13 = icmp slt i32 %12, 130
  br i1 %13, label %14, label %18

; <label>:14:                                     ; preds = %11
  store i32 80, i32* %6, align 4
  br label %15

; <label>:15:                                     ; preds = %14
  %16 = load i32, i32* %2, align 4
  %17 = add nsw i32 %16, 1
  store i32 %17, i32* %2, align 4
  br label %11

; <label>:18:                                     ; preds = %11
  br label %19

; <label>:19:                                     ; preds = %18
  %20 = load i32, i32* %1, align 4
  %21 = add nsw i32 %20, 1
  store i32 %21, i32* %1, align 4
  br label %7

; <label>:22:                                     ; preds = %7
  ret void

我的问题是标签 14 和 15。看起来标签 15 只有一个前任,即 14。既然如此,为什么我们不能将它与标签 14 合并?我假设基本的块构造是由提到的算法here 完成的。

【问题讨论】:

  • 也许编译器总是为循环增量创建一个基本块,因此如果循环中有continue,它可以将其用作跳转目标(即使没有,它也会这样做,因为它没有伤害任何东西)?或者也许代码只是这样更方便地计算出来。
  • "我假设基本块的构建是由这里提到的算法完成的。"该算法假定您将现有的三地址代码分类为基本块,但 LLVM 代码始终分为基本块。 LLVM 前端通常从 AST 转到 LLVM(我很确定 clang 无论如何都会这样做),所以我们谈论的输入仍然包含循环和 if 语句,而不是跳转。
  • @sepp2k, "我假设基本块的构建是由这里提到的算法完成的。"。我指的是那篇文章中提到的关于领导者的规则,即如何拆分成基本块。还是您的意思是 llvm 代码从 AST 转换为 CFG?
  • 我的意思是 C 代码从 AST 转换为 LLVM 代码, CFG。没有基本块的 LLVM 代码是不存在的。所以它不像C -&gt; blockless LLVM-&gt; LLVM-CFG,它只是C -&gt; LLVM,那是你的CFG。所以识别基本块不是一个单独的步骤,它是生成 LLVM 的一部分。
  • 没错,它只是从树形式到 LLVM 形式,这是一个 CFG(至少我很确定情况就是这样,因为这就是通常使用 LLVM 的方式——我实际上并没有看过在 clang 的代码中,但我怀疑 clang 会定义它自己的 TAC,然后将其转换为 LLVM,并且绝对没有 LLVM 的无块 TAC 形式。

标签: compiler-construction llvm


【解决方案1】:

(这个答案主要是对我已经在我的 cmets 中推测的内容的总结,除了它更详细并且不再推测,因为我现在实际上已经深入研究了 clang 的源代码以验证正在发生的事情)

LLVM 代码始终被结构化为基本块,并且用于在 API 中表示 LLVM 代码的类型已经形成了控制流图。没有基本块的非结构化形式的 LLVM 不存在,因此没有将非结构化 LLVM 转换为 CFG 的过程。 Clang 直接将 C AST 转换为 LLVM。因此,它不会在非结构化的三地址代码中找到基本块,它会在从 C 转换到 LLVM 的单个步骤中找到基本块。因此维基百科的算法不适用。

以下基于CodeGenFunction::EmitForStmt in CodeGen/CGStmt.cpp 的经过高度简化的伪代码概括了正在发生的事情。这是翻译for(init; cond; incr) body 形式的语句的逻辑。为简单起见,我们假设condincr 都不是空的,并且cond 是一个表达式,而不是一个声明。

  • 创建新的基本块:conditionBlockbodyBlockincrBlockexitBlock
  • 将init代码附加到当前基本块,然后跳转到conditionBlock
  • cond 的代码附加到conditionBlock,后跟br i1 %cond, label %bodyBlock, label %exitBlock
  • {break: exitBlock, continue: incrBlock} 推入中断/继续堆栈
  • body 的代码附加到bodyBlock,然后跳转到conditionBlock
  • 弹出中断/继续堆栈
  • 设置exitBlock为当前基本块

要获得您期望的输出,它必须将incr 的代码发送到bodyBlock,而不是为其设置单独的块。但随后它无法将incrBlock 推入中断/继续堆栈。当然,在您的情况下这无关紧要,因为您的代码不包含任何 continue 语句,但在一般情况下,编译器需要中断/继续堆栈来知道在 breaks 或 @ 的情况下跳转到哪里987654344@s.

因此编译器总是简单地生成这些块,然后在优化阶段合并不必要的块。

【讨论】:

    猜你喜欢
    • 2021-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多