LLVM IR alloca指令

LLVM IR alloca指令,llvm,llvm-ir,Llvm,Llvm Ir,我想为我的玩具编译器设计一个IR(比如LLVM IR),但我不知道 在进一步分析中,alloca指令的目的是什么。哪些优化alloca信息被使用 “alloca”指令在堆栈帧上分配内存 当前正在执行的功能,当 函数返回给它的调用者。该对象始终在中分配 datalayout中指示的Alloca的地址空间 “alloca”指令通常用于表示自动 必须有可用地址的变量 llvm IR手册中的一些注释: 整个LLVM文件的内容(程序集或位代码)被称为定义LLVM模块。该模块是LLVM IR顶级数据结构。

我想为我的玩具编译器设计一个IR(比如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中间表示的书,我已经更新了我的答案