zco*_*rts 9 c++ memory-management llvm clang llvm-ir
我正在玩具编译器上进步(第一次),并试图了解如何分配/构造LLVM结构类型.Kaleidoscope教程不包括甚至提及这一点,我不知道我在LLVM源/测试中寻找什么可能的例子.
所以我写了一个简单的C++例子,用clang抛弃IR,试图理解它产生的东西,但说实话,我并没有全部遵循.对我来说显而易见的事情是函数定义/声明和一些函数调用和一个memset调用,所以我得到它的一部分,但它并没有全部聚集在一起.(PS我对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;
}
Run Code Online (Sandbox Code Playgroud)
然后跑去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)"}
Run Code Online (Sandbox Code Playgroud)
有人可以解释这个IR中发生了什么以及它如何映射回C++?或者忽略这个具体的例子,一个人/应该如何为LLVM StructType分配堆内存,而这个内存与生成它的函数有关(如果你感觉很慷慨,以后如何释放内存).
我评论过的这些内容来自我最初的例子,但是从那个新手那里得到的红外线就更不具洞了......
Pup*_*ppy 10
我对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而言,结构只是一堆位,并且所有存储器位置或多或少相同.如果您希望这些位是特定的东西,那么将您想要的位存储到该位置.如果要将这些位放在堆上,则调用运行时库堆分配函数并将这些位存储到该位置.
请注意,垃圾收集是一个有点不同的故事,因为有一些尴尬的事情发生在堆栈上寻找标记的本地人.
为了记录,你不会试图理解Clang的LLVM IR.我已经这样做了好几年了,这是疯狂的,并且会花费你很长时间才能开始掌握,更不用说你不想知道的特定于C++的ABI细节了.你会在#llvm的IRC频道中进一步询问或在这里询问具体问题,而不是试图对其进行逆向工程.
我不建议查看 Clang 发出的未优化的 IR - 它太冗长了。-O1使它更具可读性 - 这是-O1带有注释行的版本(我还重新排序了两行以使其更具可读性):
%struct.Alloc = 类型 { i32 } ; 定义分配类型。
定义 noalias %struct.Alloc* @_Z8allocPtrv() #0 {
%1 = 尾调用 noalias i8* @_Znwj(i32 4) #2 ; 调用_Znwj(4)。这将返回 i8*。
%3 = bitcast i8* %1 到 i32* ;将返回值转换为 i32* (int*)...
store i32 0, i32* %3, align 4 ; ...并将其内容归零。
%2 = bitcast i8* %1 到 %struct.Alloc* ;将返回值转换为 Alloc*...
ret %struct.Alloc* %2 ; ……然后退货。
}
; 声明_Znwj 函数。这不需要定义,因为它已经定义了
; 在 libstdc++ 中:这是“operator new”。您可以通过将此字符串传递给
; C++ demangler,例如 http://demangler.com/ 上的那个。
声明 noalias i8* @_Znwj(i32) #1
定义 i32 @main() #0 {
%1 = 尾调用 %struct.Alloc* @_Z8allocPtrv() ; 调用 _Z8allocPtrv(如上定义)。
返回 i32 0
}
这是一个new调用,不是本地分配,所以在离开时不会被清除@_Z8allocPtrv。本地分配确实在 LLVM IR 中使用alloca指令执行,而不是new调用。
如果你很好奇怎样 new的作品,我相信它的标准实现使用malloc,这是由编译器编译已编译的库函数的一些包括系统调用(一个或多个)。