了解内存分配的工作原理 (LLVM)
Understanding how memory allocation works (LLVM)
我正在玩具编译器上取得进展(第一次),并试图了解如何 allocate/construct LLVM 结构类型。
Kaleidoscope 教程没有包括甚至没有提到这一点,我不知道我在 LLVM source/tests 中寻找什么来找到可能的例子。
所以我写了一个简单的 C++ 示例,用 clang 转储了 IR,试图理解它产生了什么,但老实说,我并没有完全遵循它。对我来说显而易见的是函数 definition/declarations 和一些函数调用以及 memset
调用,所以我得到了它的一部分,但它还没有全部组合在一起。 (P.S 我对 alloca 指令文档的解释是,从中创建的任何东西都会在 return 上被释放,所以我不能使用那个权利,它基本上只用于局部变量?)
我所做的是:
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++?或者忽略这个具体的例子 would/should 如何为一个 LLVM StructType 分配堆内存,这个 LLVM StructType 在创建它的函数中存在(如果你觉得慷慨,以后如何释放内存)。
我注释掉的部分来自我的原始示例,但作为一个完全的新手,IR 的洞察力更差...
my interpretation of the alloca instruction docs is that it anything
created from that gets freed on return so I can't use that right, it's
essentially only for local variables?
是的。此外,目前关于 LLVM IR 的建议是,尽管 alloca
可以按您预期的那样工作,但优化是另一种情况。他们建议您 alloca
立即在入口块中输入所有本地信息,即使您不允许用户访问它们或它们并不总是包含有意义的数据。
堆分配是库的一项功能。它不是 LLVM 或编译器的特性。当你使用 new T()
时,编译器只是调用 operator new
来获取内存,然后在那里构造 T
。没有魔法参与。你看到的大多数垃圾都是特定于 C++-ABI 的,而不是 LLVM 的任何要求。它最终会降低到 void* p = malloc(size); new(p) T();
之类的东西。对于几乎所有类型 T
,这几乎可以归结为一系列存储到 p
或调用用户定义的函数。
您可以使用您选择的运行时库中的内存分配函数。
trying to understand how to allocate/construct an LLVM struct type
LLVM 类型系统不包括构造的概念。这是源语言的概念。
就 LLVM 而言,结构只是一堆位,所有内存位置都或多或少相同。如果您希望这些位是特定的东西,请将您想要的位存储到该位置。如果要将这些位放在堆上,则调用运行时库堆分配函数并将这些位存储到该位置。
但是请注意,垃圾回收是一个稍微不同的故事,因为发生了一些尴尬的事情 w.r.t。在堆栈上找到本地人进行标记。
郑重声明,您不会试图理解 Clang 的 LLVM IR。我已经这样做了好几年了,这太疯狂了,你会花那么长时间才能开始掌握,更不用说你不想知道的所有 C++ 特定的 ABI 细节了。你会在他们的 IRC 频道的 #llvm 中得到更多的询问,或者在这里提出具体问题,而不是试图对其进行逆向工程。
我不建议查看 Clang 发出的未优化的 IR - 它太冗长了。 -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
,它是由编译库的编译器翻译的某些包含系统调用的函数。
我正在玩具编译器上取得进展(第一次),并试图了解如何 allocate/construct LLVM 结构类型。 Kaleidoscope 教程没有包括甚至没有提到这一点,我不知道我在 LLVM source/tests 中寻找什么来找到可能的例子。
所以我写了一个简单的 C++ 示例,用 clang 转储了 IR,试图理解它产生了什么,但老实说,我并没有完全遵循它。对我来说显而易见的是函数 definition/declarations 和一些函数调用以及 memset
调用,所以我得到了它的一部分,但它还没有全部组合在一起。 (P.S 我对 alloca 指令文档的解释是,从中创建的任何东西都会在 return 上被释放,所以我不能使用那个权利,它基本上只用于局部变量?)
我所做的是:
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++?或者忽略这个具体的例子 would/should 如何为一个 LLVM StructType 分配堆内存,这个 LLVM StructType 在创建它的函数中存在(如果你觉得慷慨,以后如何释放内存)。
我注释掉的部分来自我的原始示例,但作为一个完全的新手,IR 的洞察力更差...
my interpretation of the alloca instruction docs is that it anything created from that gets freed on return so I can't use that right, it's essentially only for local variables?
是的。此外,目前关于 LLVM IR 的建议是,尽管 alloca
可以按您预期的那样工作,但优化是另一种情况。他们建议您 alloca
立即在入口块中输入所有本地信息,即使您不允许用户访问它们或它们并不总是包含有意义的数据。
堆分配是库的一项功能。它不是 LLVM 或编译器的特性。当你使用 new T()
时,编译器只是调用 operator new
来获取内存,然后在那里构造 T
。没有魔法参与。你看到的大多数垃圾都是特定于 C++-ABI 的,而不是 LLVM 的任何要求。它最终会降低到 void* p = malloc(size); new(p) T();
之类的东西。对于几乎所有类型 T
,这几乎可以归结为一系列存储到 p
或调用用户定义的函数。
您可以使用您选择的运行时库中的内存分配函数。
trying to understand how to allocate/construct an LLVM struct type
LLVM 类型系统不包括构造的概念。这是源语言的概念。
就 LLVM 而言,结构只是一堆位,所有内存位置都或多或少相同。如果您希望这些位是特定的东西,请将您想要的位存储到该位置。如果要将这些位放在堆上,则调用运行时库堆分配函数并将这些位存储到该位置。
但是请注意,垃圾回收是一个稍微不同的故事,因为发生了一些尴尬的事情 w.r.t。在堆栈上找到本地人进行标记。
郑重声明,您不会试图理解 Clang 的 LLVM IR。我已经这样做了好几年了,这太疯狂了,你会花那么长时间才能开始掌握,更不用说你不想知道的所有 C++ 特定的 ABI 细节了。你会在他们的 IRC 频道的 #llvm 中得到更多的询问,或者在这里提出具体问题,而不是试图对其进行逆向工程。
我不建议查看 Clang 发出的未优化的 IR - 它太冗长了。 -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
,它是由编译库的编译器翻译的某些包含系统调用的函数。