首页 > 解决方案 > LLVM 如何避免为 `br` IR 指令生成冗余的本机代码?

问题描述

对于以下 C 代码

void foo() {
    int forty_two = 42;
    if (forty_two == 42) {
    }
}

clang -S -emit-llvm foo.c发出此 IR 代码:

define dso_local void @foo() #0 {
  %1 = alloca i32, align 4
  store i32 42, i32* %1, align 4
  %2 = load i32, i32* %1, align 4
  %3 = icmp eq i32 %2, 42
  br i1 %3, label %4, label %5

4:                                                ; preds = %0
  br label %5

5:                                                ; preds = %4, %0
  ret void
}

对于 IR,LLVM ( llc foo.ll) 生成下面的 x64 代码。为了便于阅读,它被删减了。

foo:                                    # @foo
# %bb.0:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $42, -4(%rbp)
    cmpl    $42, -4(%rbp)
    jne .LBB0_2
# %bb.1:
    jmp .LBB0_2
.LBB0_2:
    popq    %rbp
    retq

与 LLVM 发出的本机代码相比,以直接方式翻译 IR 代码将包含许多冗余指令。这些方面的东西:

foo:
# %bb.0:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $42, -4(%rbp)
    cmpl    $42, -4(%rbp)

# create the i1 boolean in a register.
# (This instruction is redundant and LLVM doesn't emit is)
    sete    %al
# See whether the comparison's result was `true` or `false`.
# (This instruction is redundant and LLVM doesn't emit it)
    cmpb    $1, %al

    jne .LBB0_2
# %bb.1:
    jmp .LBB0_2
.LBB0_2:
    popq    %rbp
    retq

我的问题是: 确保不发出这些冗余指令的 LLVM 代码部分在哪里?它是如何工作的?

我阅读了@Eli Bendersky 的一篇精彩的LLVM 指令生命周期文章,并查看了 和 中的SelectionDAGBuilder::visitICmp代码SelectionDAGBuilder::visitBr。但是我自己没有设法找出答案。

标签: assemblycompiler-constructionllvmcompiler-optimizationllvm-ir

解决方案


TLDR:X86FastISel::X86SelectBranch

LLVM 的 discord 上有人告诉我-print-after-all关于llc. (实际上,@arnt 甚至在我提出不和谐问题之前就在他们的回答中提到了它,我不知道为什么我没有立即尝试使用该标志......)

该标志让我看到“X86 DAG->DAG 指令选择”是第一次通过,它不仅转换了 IR,而且将其转换为 x86 特定的机器 IR (MIR)。(对应的类是X86DAGToDAGISel)。
从它发出的 MIR 可以清楚地看出,是否发出SETCC/TEST指令的决定发生在传递运行期间。

一步一步X86DAGToDAGISel::runOnMachineFunction最终把我带到了X86FastISel::X86SelectBranch。那里,

  • 如果br是 的icmp结果的唯一用户并且指令在同一个基本块中,则通行证决定发出SETCC/TEST
  • 如果icmp's 结果有其他用户或两个 IR 指令不在同一个基本块中,则 pass 实际上会发出SETCC/ TEST

所以,对于这个 C 代码:

void foo() {
    int forty_two = 42;
    int is_forty_two;
    if (is_forty_two = (forty_two == 42)) {
    }
}

clang -S -emit-llvm brcond.c产生以下 IR:

define void @foo() #0 {
entry:
  %forty_two = alloca i32, align 4
  %is_forty_two = alloca i32, align 4
  store i32 42, i32* %forty_two, align 4
  %0 = load i32, i32* %forty_two, align 4
  %cmp = icmp eq i32 %0, 42
  %conv = zext i1 %cmp to i32
  store i32 %conv, i32* %is_forty_two, align 4
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  br label %if.end

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

显然,%cmp拥有不止一个用户。所以llc brcond.ll发出下面的程序集(略略):

foo:                                    # @foo
# %bb.0:                                # %entry
    pushq   %rax
    movl    $42, (%rsp)
    cmpl    $42, (%rsp)
    sete    %al
    movb    %al, %cl
    andb    $1, %cl
    movzbl  %cl, %ecx
    movl    %ecx, 4(%rsp)
    testb   $1, %al
    jne .LBB1_1
    jmp .LBB1_2
.LBB1_1:                                # %if.then
    jmp .LBB1_2
.LBB1_2:                                # %if.end
    popq    %rax
    retq

推荐阅读