I want to design a IR (like LLVM IR) for my toy compiler and I don't know
what is the purpose of the alloca
instruction in further analysis. In which optimizations alloca
informations are used?
答案 0 :(得分:5)
'alloca'指令在堆栈帧上分配内存 当前正在执行的功能,在此时自动释放 函数返回其调用者。该对象始终在。中分配 datalayout中指示的allocas的地址空间。
'alloca'指令通常用于表示自动 必须有可用地址的变量。
llvm IR书中的一些注释:
整个LLVM文件(程序集或bitcode)的内容称为定义LLVM模块。该模块是LLVM IR顶级数据结构。每个模块都包含一系列函数,其中包含一系列包含指令序列的基本块。 该模块还包含支持此模型的外围实体,例如全局变量,目标数据布局,外部函数原型以及数据结构声明。
所以alloca指令(在我的下面)只是为了支持IR。
例如以下代码:
int sum(int a, int b) {
return a+b;
}
在IR中看起来像:
; Function Attrs: noinline nounwind uwtable
define i32 @sum(int, int)(i32, i32) #0 !dbg !6 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
call void @llvm.dbg.declare(metadata i32* %3, metadata !10, metadata !11), !dbg !12
store i32 %1, i32* %4, align 4
call void @llvm.dbg.declare(metadata i32* %4, metadata !13, metadata !11), !dbg !14
%5 = load i32, i32* %3, align 4, !dbg !15
%6 = load i32, i32* %4, align 4, !dbg !16
%7 = add nsw i32 %5, %6, !dbg !17
ret i32 %7, !dbg !18
}
alloca指令在堆栈帧上保留空间 当前功能。空间量由元素类型决定 大小,它尊重指定的对齐方式。第一条指令, %a.addr = alloca i32,align 4,分配一个4字节的堆栈元素,其中 尊重4字节对齐。存储指向堆栈元素的指针 在本地标识符中,%a.addr。 alloca指令通常是 用于表示本地(自动)变量。
答案 1 :(得分:3)
alloca
指令的目的是使为具有可变变量的命令式语言生成代码变得更加容易。如果没有它,您将需要在 SSA form 中组织所有您的作业。对于可变变量,这可能会变得非常复杂。
让我们看一下这个简单的 C 程序(取自 LLVM docs)并关注变量 X 及其赋值。如您所见,它的最终值(我们返回的值)取决于程序中采用的 if 分支。
int G, H;
int test(_Bool Condition) {
int X;
if (Condition)
X = G;
else
X = H;
return X;
}
在不使用 alloca
现在,如果我们将这个程序翻译成 LLVM IR,我们会得到如下结果:
@G = weak global i32 0 ; type of @G is i32*
@H = weak global i32 0 ; type of @H is i32*
define i32 @test(i1 %Condition) {
entry:
br i1 %Condition, label %cond_true, label %cond_false
cond_true:
%X.0 = load i32* @G
br label %cond_next
cond_false:
%X.1 = load i32* @H
br label %cond_next
cond_next:
%X.2 = phi i32 [ %X.1, %cond_false ], [ %X.0, %cond_true ]
ret i32 %X.2
}
因为在返回指令之前 X 有两个不同的可能值,我们必须插入一个 Phi node 来合并这两个值。这是为什么?嗯,因为 LLVM 要求所有分配都采用 SSA 形式,而 Phi 节点是合并两个值的唯一方法。然而,这种带有 Phi 节点的 SSA 构建需要非平凡的算法,并且为每个编译器重新实现是不方便和浪费的。
那么,您可能想知道我们如何解决这个问题?正如您可能已经猜到的那样,答案是alloca
!
使用 alloca
这里的“技巧”是,虽然 LLVM 确实要求所有寄存器值都采用 SSA 形式,但它不要求(或允许)内存对象采用 SSA 形式。考虑到这一点,高级思想是我们希望为函数中的每个可变对象创建一个堆栈变量(位于内存中)。使用它,我们的代码现在变成:
@G = weak global i32 0 ; type of @G is i32*
@H = weak global i32 0 ; type of @H is i32*
define i32 @test(i1 %Condition) {
entry:
%X = alloca i32 ; type of %X is i32*.
br i1 %Condition, label %cond_true, label %cond_false
cond_true:
%X.0 = load i32* @G
store i32 %X.0, i32* %X ; Update X
br label %cond_next
cond_false:
%X.1 = load i32* @H
store i32 %X.1, i32* %X ; Update X
br label %cond_next
cond_next:
%X.2 = load i32* %X ; Read X
ret i32 %X.2
}
由此,我们发现了一种无需创建 Phi 节点即可处理任意可变变量的方法!
进一步(为了好奇):
虽然这个解决方案解决了我们眼前的问题,但它引入了另一个问题:我们现在显然为非常简单和常见的操作引入了大量堆栈流量,这是一个主要的性能问题。对我们来说幸运的是,LLVM 优化器有一个名为“mem2reg”的高度优化的优化通道来处理这种情况,将这样的 allocas 提升到 SSA 寄存器中,并在适当的时候插入 Phi 节点。