LLVM IR alloca指令
我想为我的玩具编译器设计一个IR(比如LLVM IR),但我不知道 在进一步分析中,LLVM IR alloca指令,llvm,llvm-ir,Llvm,Llvm Ir,我想为我的玩具编译器设计一个IR(比如LLVM IR),但我不知道 在进一步分析中,alloca指令的目的是什么。哪些优化alloca信息被使用 “alloca”指令在堆栈帧上分配内存 当前正在执行的功能,当 函数返回给它的调用者。该对象始终在中分配 datalayout中指示的Alloca的地址空间 “alloca”指令通常用于表示自动 必须有可用地址的变量 llvm IR手册中的一些注释: 整个LLVM文件的内容(程序集或位代码)被称为定义LLVM模块。该模块是LLVM IR顶级数据结构。
alloca
指令的目的是什么。哪些优化alloca
信息被使用
“alloca”指令在堆栈帧上分配内存
当前正在执行的功能,当
函数返回给它的调用者。该对象始终在中分配
datalayout中指示的Alloca的地址空间
“alloca”指令通常用于表示自动
必须有可用地址的变量
llvm IR手册中的一些注释:
整个LLVM文件的内容(程序集或位代码)被称为定义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指令通常是
用于表示局部(自动)变量
TL;博士
alloca
指令的目的是使使用可变变量为命令式语言生成代码变得更容易。没有它,你就需要把你所有的作业都组织起来。对于可变变量,这会变得非常复杂
例子 让我们看一下这个简单的C程序(取自),重点关注变量X及其赋值。正如您所注意到的,它的最终值(我们返回的值)取决于程序中的if分支
int G, H;
int test(_Bool Condition) {
int X;
if (Condition)
X = G;
else
X = H;
return X;
}
不使用alloca将示例程序转换为LLVM IR
现在,如果我们将此程序转换为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有两个不同的可能值,所以我们必须插入一个来合并这两个值。为什么呢?因为LLVM要求所有赋值都是SSA形式,而Phi节点是合并两个值的唯一方法。然而,这种带有Phi节点的SSA构造需要非平凡的算法,并且对于每个编译器来说重新实现是不方便和浪费的
所以,你可能想知道我们如何解决这个问题?正如你可能已经猜到的,答案是alloca
使用alloca将示例程序转换为LLVM IR
这里的“诀窍”是,尽管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”的高度优化的优化过程,可以处理这种情况,将这样的alloca提升到SSA寄存器中,并适当插入Phi节点。谢谢,我看到了,但我不清楚为什么该指令必须明确存在于IR中。我向您推荐一本介绍LLVM中间表示的书,我已经更新了我的答案