LLVM 内置函数

8
使用LLVM构建项目时,某些函数调用将被替换为内在函数。这种替换是由前端(例如clang)还是LLVM后端完成的?
互联网上的讨论表明,内在函数的替换与优化选项有关。那么这是否意味着如果没有优化选项,就不会进行内在替换?或者实际上存在一些默认的内部函数替换无法禁用?
如果有任何方法可以禁用所有内在函数,则应如何操作?
1个回答

14
依情况而定。在代码中编写的内部函数直接通过前端发出。像 llvm.memset 这样的内部函数是在 IR 级别进行优化时引入到代码中的(既不是前端也不是后端执行这些优化)。
以下是一个(相当愚蠢的)例子:
int main(int argc, char** argv)
{
        int a[8];

        for (int i = 0; i != 8; ++i)
                a[i] = 0;

        for (int i = 7; i >= 0; --i)
                a[i] = a[i+1] + argc;

        return a[0];
}

使用clang 3.5(clang -S -emit-llvm)编译,您将获得以下IR而不需要任何内部函数:

; Function Attrs: nounwind uwtable
define i32 @main(i32 %argc, i8** %argv) #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  %3 = alloca i8**, align 8
  %a = alloca [8 x i32], align 16
  %i = alloca i32, align 4
  %i1 = alloca i32, align 4
  store i32 0, i32* %1
  store i32 %argc, i32* %2, align 4
  store i8** %argv, i8*** %3, align 8
  store i32 0, i32* %i, align 4
  br label %4

; <label>:4                                       ; preds = %11, %0
  %5 = load i32* %i, align 4
  %6 = icmp ne i32 %5, 8
  br i1 %6, label %7, label %14

; <label>:7                                       ; preds = %4
  %8 = load i32* %i, align 4
  %9 = sext i32 %8 to i64
  %10 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 %9
  store i32 0, i32* %10, align 4
  br label %11

; <label>:11                                      ; preds = %7
  %12 = load i32* %i, align 4
  %13 = add nsw i32 %12, 1
  store i32 %13, i32* %i, align 4
  br label %4

; <label>:14                                      ; preds = %4
  store i32 7, i32* %i1, align 4
  br label %15

; <label>:15                                      ; preds = %29, %14
  %16 = load i32* %i1, align 4
  %17 = icmp sge i32 %16, 0
  br i1 %17, label %18, label %32

; <label>:18                                      ; preds = %15
  %19 = load i32* %i1, align 4
  %20 = add nsw i32 %19, 1
  %21 = sext i32 %20 to i64
  %22 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 %21
  %23 = load i32* %22, align 4
  %24 = load i32* %2, align 4
  %25 = add nsw i32 %23, %24
  %26 = load i32* %i1, align 4
  %27 = sext i32 %26 to i64
  %28 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 %27
  store i32 %25, i32* %28, align 4
  br label %29

; <label>:29                                      ; preds = %18
  %30 = load i32* %i1, align 4
  %31 = add nsw i32 %30, -1
  store i32 %31, i32* %i1, align 4
  br label %15

; <label>:32                                      ; preds = %15
  %33 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 0
  %34 = load i32* %33, align 4
  ret i32 %34
}

使用clang -emit-llvm -O1重新编译,你会看到这个:

; Function Attrs: nounwind readnone uwtable
define i32 @main(i32 %argc, i8** nocapture readnone %argv) #0 {
.preheader:
  %a = alloca [8 x i32], align 16
  %a6 = bitcast [8 x i32]* %a to i8*
  call void @llvm.memset.p0i8.i64(i8* %a6, i8 0, i64 32, i32 4, i1 false)
  br label %0

; <label>:0                                       ; preds = %.preheader, %0
  %indvars.iv = phi i64 [ 7, %.preheader ], [ %indvars.iv.next, %0 ]
  %1 = add nsw i64 %indvars.iv, 1
  %2 = getelementptr inbounds [8 x i32]* %a, i64 0, i64 %1
  %3 = load i32* %2, align 4, !tbaa !1
  %4 = add nsw i32 %3, %argc
  %5 = getelementptr inbounds [8 x i32]* %a, i64 0, i64 %indvars.iv
  store i32 %4, i32* %5, align 4, !tbaa !1
  %indvars.iv.next = add nsw i64 %indvars.iv, -1
  %6 = trunc i64 %indvars.iv to i32
  %7 = icmp sgt i32 %6, 0
  br i1 %7, label %0, label %8

; <label>:8                                       ; preds = %0
  %9 = getelementptr inbounds [8 x i32]* %a, i64 0, i64 0
  %10 = load i32* %9, align 16, !tbaa !1
  ret i32 %10
}

初始化循环已被llvm.memset内置函数所替代。后端可以自由处理内置函数,但通常llvm.memset会降级为libc库调用。

回答您的第一个问题:是的,如果您不优化代码,则IR中不会出现内置函数。

要防止在代码中引入内置函数,您只需要找到IR上的优化通道并不运行它。以下是相关问题:如何找出在IR上完成了哪些传递: Where to find the optimization sequence for clang -OX?

对于-O1,我们得到:

prune-eh -inline-cost -always-inline -functionattrs -sroa -domtree -early-cse -lazy-value-info -jump-threading -correlated-propagation -simplifycfg -instcombine -tailcallelim -simplifycfg -reassociate -domtree -loops -loop-simplify -lcssa -loop-rotate -licm -loop-unswitch -instcombine -scalar-evolution -lcssa -indvars -loop-idiom -loop-deletion -loop-unroll -memdep -memcpyopt -sccp -instcombine -lazy-value-info -jump-threading -correlated-propagation -domtree -memdep -dse -adce -simplifycfg -instcombine -barrier -domtree -loops -loop-simplify -lcssa -branch-prob -block-freq -scalar-evolution -loop-vectorize -instcombine -simplifycfg -strip-dead-prototypes -verify

猜测:instcombine引入了llvm.memset。我在未优化的IR上运行这些传递,不使用instcombine和opt,得到如下结果:

; Function Attrs: nounwind readnone uwtable
define i32 @main(i32 %argc, i8** %argv) #0 {
  %a = alloca [8 x i32], align 16
  %1 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 8
  %2 = load i32* %1, align 4
  %3 = add nsw i32 %2, %argc
  %4 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 7
  store i32 %3, i32* %4, align 4
  %5 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 7
  %6 = load i32* %5, align 4
  %7 = add nsw i32 %6, %argc
  %8 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 6
  store i32 %7, i32* %8, align 4
  %9 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 6
  %10 = load i32* %9, align 4
  %11 = add nsw i32 %10, %argc
  %12 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 5
  store i32 %11, i32* %12, align 4
  %13 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 5
  %14 = load i32* %13, align 4
  %15 = add nsw i32 %14, %argc
  %16 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 4
  store i32 %15, i32* %16, align 4
  %17 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 4
  %18 = load i32* %17, align 4
  %19 = add nsw i32 %18, %argc
  %20 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 3
  store i32 %19, i32* %20, align 4
  %21 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 3
  %22 = load i32* %21, align 4
  %23 = add nsw i32 %22, %argc
  %24 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 2
  store i32 %23, i32* %24, align 4
  %25 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 2
  %26 = load i32* %25, align 4
  %27 = add nsw i32 %26, %argc
  %28 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 1
  store i32 %27, i32* %28, align 4
  %29 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 1
  %30 = load i32* %29, align 4
  %31 = add nsw i32 %30, %argc
  %32 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 0
  store i32 %31, i32* %32, align 4
  %33 = getelementptr inbounds [8 x i32]* %a, i32 0, i64 0
  %34 = load i32* %33, align 4
  ret i32 %34
}

没有指令。因此,为了防止你的代码中运行(至少是memset)内在函数,不要对IR运行instcombine。但是,instcombine是一个强大的优化工具,真正缩短了代码。

现在你有两个选择:

  1. 不使用引入内在函数的优化工具
  2. 编写自己的llvm opt工具,将内在函数转换回任何可以被替换的地方,并在优化之后,在后端开始工作之前运行它。

希望这能在某种程度上帮助到你。干杯!


即使使用 -O0,clang 5仍会生成内部函数。 - Tong Zhou
@TongZhou 如果能提供一些来自clang 5+的内置示例就太好了。 - Damian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接