LLVM 如何避免为 `br` IR 指令生成冗余的本机代码?
How does LLVM avoid generating redundant native code for the `br` IR instruction?
对于下面的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 , -4(%rbp)
cmpl , -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 , -4(%rbp)
cmpl , -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 , %al
jne .LBB0_2
# %bb.1:
jmp .LBB0_2
.LBB0_2:
popq %rbp
retq
我的问题是:
确保不发出这些冗余指令的 LLVM 代码部分在哪里?它是如何工作的?
我阅读了@Eli Bendersky 的优秀 post Life of an instruction in LLVM 并查看了 SelectionDAGBuilder::visitICmp
和 SelectionDAGBuilder::visitBr
中的代码。但是我自己没有找到答案。
LLVM 运行以有益的方式更改代码的过程。每一次通过都决定了“有益”的含义。我是否正确地假设您对通用答案更感兴趣并以 br
为例?如果是这样,指示编译器在每次通过后打印 IR 的 -print-after-all
标志可能就是您想要的。还有一个 -print-before-all
和更具体的标志。
阅读输出并seeing how it changes gfives you a well overview of which passes add/eliminate which warts.
TLDR:X86FastISel::X86SelectBranch
有人在 LLVM 的 discord 上告诉我 llc
的 -print-after-all
标志。 (事实上 ,@arnt甚至在我提出不和谐问题之前就在他们的回答中提到了它,我不知道为什么我没有立即试一试...)
那个标志让我看到“X86 DAG->DAG 指令选择”是第一个不仅转换 IR,而且把它变成 x86 特定机器 IR (MIR) 的通道。 (对应的class为X86DAGToDAGISel
)。
从它发出的 MIR,很明显决定发出或不发出 SETCC
/TEST
指令发生在传递 运行.
期间
逐步完成 X86DAGToDAGISel::runOnMachineFunction
最终将我带到了 X86FastISel::X86SelectBranch
。在那里,
- 如果
br
是icmp
结果的唯一用户并且指令在同一个基本块中,则pass决定不发出 SETCC
/TEST
- 如果
icmp
的结果有其他用户或两个 IR 指令不在同一个基本块中,则通行证实际上会发出 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 , (%rsp)
cmpl , (%rsp)
sete %al
movb %al, %cl
andb , %cl
movzbl %cl, %ecx
movl %ecx, 4(%rsp)
testb , %al
jne .LBB1_1
jmp .LBB1_2
.LBB1_1: # %if.then
jmp .LBB1_2
.LBB1_2: # %if.end
popq %rax
retq
对于下面的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 , -4(%rbp)
cmpl , -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 , -4(%rbp)
cmpl , -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 , %al
jne .LBB0_2
# %bb.1:
jmp .LBB0_2
.LBB0_2:
popq %rbp
retq
我的问题是: 确保不发出这些冗余指令的 LLVM 代码部分在哪里?它是如何工作的?
我阅读了@Eli Bendersky 的优秀 post Life of an instruction in LLVM 并查看了 SelectionDAGBuilder::visitICmp
和 SelectionDAGBuilder::visitBr
中的代码。但是我自己没有找到答案。
LLVM 运行以有益的方式更改代码的过程。每一次通过都决定了“有益”的含义。我是否正确地假设您对通用答案更感兴趣并以 br
为例?如果是这样,指示编译器在每次通过后打印 IR 的 -print-after-all
标志可能就是您想要的。还有一个 -print-before-all
和更具体的标志。
阅读输出并seeing how it changes gfives you a well overview of which passes add/eliminate which warts.
TLDR:X86FastISel::X86SelectBranch
有人在 LLVM 的 discord 上告诉我 llc
的 -print-after-all
标志。 (事实上 ,@arnt甚至在我提出不和谐问题之前就在他们的回答中提到了它,我不知道为什么我没有立即试一试...)
那个标志让我看到“X86 DAG->DAG 指令选择”是第一个不仅转换 IR,而且把它变成 x86 特定机器 IR (MIR) 的通道。 (对应的class为X86DAGToDAGISel
)。
从它发出的 MIR,很明显决定发出或不发出 SETCC
/TEST
指令发生在传递 运行.
逐步完成 X86DAGToDAGISel::runOnMachineFunction
最终将我带到了 X86FastISel::X86SelectBranch
。在那里,
- 如果
br
是icmp
结果的唯一用户并且指令在同一个基本块中,则pass决定不发出SETCC
/TEST
- 如果
icmp
的结果有其他用户或两个 IR 指令不在同一个基本块中,则通行证实际上会发出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 , (%rsp)
cmpl , (%rsp)
sete %al
movb %al, %cl
andb , %cl
movzbl %cl, %ecx
movl %ecx, 4(%rsp)
testb , %al
jne .LBB1_1
jmp .LBB1_2
.LBB1_1: # %if.then
jmp .LBB1_2
.LBB1_2: # %if.end
popq %rax
retq