了解内存分配的工作原理(LLVM)

时间:2015-11-04 22:52:34

标签: c++ memory-management clang llvm llvm-ir

我在玩具编译器上取得了进展(第一次),并试图了解如何分配/构建LLVM结构类型。 Kaleidoscope教程没有包括甚至提及这一点,我不知道我在LLVM源/测试中寻找的东西,以找到可能的例子。

所以我写了一个简单的C ++示例,用clang抛弃了IR,试图理解它产生的东西,但说实话,我不能完全遵循它。对我来说显而易见的事情是函数定义/声明和一些函数调用以及memset调用,所以我得到了它的一部分,但它并没有为我所有。 (P.S我对alloca指令文档的解释是,由此创建的任何内容在返回时都会被释放,因此我无法使用该权限,它本质上仅适用于局部变量?)

我所做的是:

alloc.cpp

struct Alloc {
  int age;
};

//Alloc allocCpy() {
//  return *new Alloc();
//}

Alloc *allocPtr() {
  return new Alloc();
}

int main() {
  Alloc *ptr = allocPtr();
//  ptr->name = "Courtney";
//  Alloc cpy = allocCpy();
//  cpy.name = "Robinson";
//  std::cout << ptr->name << std::endl;
//  std::cout << cpy.name << std::endl;
  return 0;
}

然后运行clang -S -emit-llvm alloc.cpp以生成alloc.ll

; ModuleID = 'alloc.cpp'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.11.0"

%struct.Alloc = type { i32 }

; Function Attrs: ssp uwtable
define %struct.Alloc* @_Z8allocPtrv() #0 {
entry:
  %call = call noalias i8* @_Znwm(i64 4) #3
  %0 = bitcast i8* %call to %struct.Alloc*
  %1 = bitcast %struct.Alloc* %0 to i8*
  call void @llvm.memset.p0i8.i64(i8* %1, i8 0, i64 4, i32 4, i1 false)
  ret %struct.Alloc* %0
}

; Function Attrs: nobuiltin
declare noalias i8* @_Znwm(i64) #1

; Function Attrs: nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture, i8, i64, i32, i1) #2

; Function Attrs: ssp uwtable
define i32 @main() #0 {
entry:
  %retval = alloca i32, align 4
  %ptr = alloca %struct.Alloc*, align 8
  store i32 0, i32* %retval
  %call = call %struct.Alloc* @_Z8allocPtrv()
  store %struct.Alloc* %call, %struct.Alloc** %ptr, align 8
  ret i32 0
}

attributes #0 = { ssp uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="core2" "target-features"="+cx16,+sse,+sse2,+sse3,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nobuiltin "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="core2" "target-features"="+cx16,+sse,+sse2,+sse3,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { nounwind }
attributes #3 = { builtin }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"PIC Level", i32 2}
!1 = !{!"clang version 3.7.0 (tags/RELEASE_370/final)"}

有人可以解释这个IR中发生的事情以及它如何映射回C ++吗?或者忽略这个具体的例子,一个人/应该如何为LLVM StructType分配堆内存,而这个内存就是生成它的函数(如果你感觉很慷慨,以后如何释放内存)。

我已经评论过的内容来自我原来的例子,但是从那个新手那里得到的IR就更不具洞了......

2 个答案:

答案 0 :(得分:9)

  

我对alloca指令文档的解释是它的任何东西   从那里创建的东西在返回时被释放,所以我不能正确使用它,它就是   基本上只对局部变量?

是。此外,关于LLVM IR的当前建议是,虽然alloca按预期工作,但优化是另一种情况。即使您不允许用户访问它们,或者它们并不总是包含有意义的数据,他们会立即通知您alloca条目块中的所有本地人。

堆分配是一个库功能。它不是LLVM或编译器的功能。当您使用new T()时,编译器只需调用operator new来获取内存,然后在那里构造T。没有魔法参与。你在那里看到的大多数垃圾都是C ++ - ABI特定而不是LLVM的任何要求。它最终降低到像void* p = malloc(size); new(p) T();这样的东西。对于几乎所有类型T,这几乎归结为p中的一系列商店或调用用户定义的函数。

您可以使用所选运行时库中的内存分配功能。

  

试图了解如何分配/构造LLVM结构类型

LLVM类型系统不包括构造的概念。这是源语言的概念。

就LLVM而言,结构只是一堆比特,并且所有内存位置或多或少相同。如果您希望这些位是特定的东西,那么将您想要的位存储到该位置。如果要将这些位放在堆上,则调用运行时库堆分配函数并将这些位存储到该位置。

请注意,垃圾收集是一个有点不同的故事,因为w.r.t上有一些尴尬的东西。在堆栈上寻找标记的本地人。

为了记录,你试图了解Clang的LLVM IR。我已经这样做了好几年了,这是疯狂的,并且会花费你很长时间才能开始掌握,更不用说你不想知道的特定于C ++的ABI细节了。你会在#llvm的IRC频道中进一步询问或在这里询问具体问题,而不是试图对其进行逆向工程。

答案 1 :(得分:3)

我不建议看看Clang发出的未经优化的红外线 - 它太冗长了。 -O1使其更具可读性 - 这是-O1版本,其中注释注释了这些行(我还重新排序了两行以使其更具可读性):

%struct.Alloc = type { i32 }                   ; Define the Alloc type.

define noalias %struct.Alloc* @_Z8allocPtrv() #0 {
  %1 = tail call noalias i8* @_Znwj(i32 4) #2  ; Call _Znwj(4). This retuns i8*.
  %3 = bitcast i8* %1 to i32*                  ; Cast the returned value to i32* (int*)...
  store i32 0, i32* %3, align 4                ; ...and zero its content.
  %2 = bitcast i8* %1 to %struct.Alloc*        ; Cast the returned value to Alloc*...
  ret %struct.Alloc* %2                        ; ...and return it.
}

; Declare the _Znwj function. This doesn't need to be defined since it's already defined
; in libstdc++: this is 'operator new'. You can see this by passing this string through a
; C++ demangler, for example the one at http://demangler.com/.
declare noalias i8* @_Znwj(i32) #1

define i32 @main() #0 {
  %1 = tail call %struct.Alloc* @_Z8allocPtrv()  ; Call _Z8allocPtrv (Defined above).
  ret i32 0
}

这是new来电,而不是本地分配,因此离开@_Z8allocPtrv时不会被清除。本地分配确实在LLVM IR中使用alloca指令执行,而不是new调用。

如果您对 new的工作方式感到好奇,我相信它的标准实现使用malloc,它由编译器转换为包含某些功能的编译器。系统调用。