了解内存分配的工作原理(LLVM)
Understanding how memory allocation works (LLVM)
我(第一次)在玩具编译器方面取得了进展,并试图了解如何分配/构造LLVM结构类型。Kaleidoscope教程没有包括甚至没有提到这一点,我不知道我在LLVM源代码/测试中寻找什么来找到可能的例子。
所以我写了一个简单的C++示例,用clang丢弃了IR,试图理解它产生了什么,但老实说,我并没有完全遵循它。对我来说显而易见的是函数定义/声明、一些函数调用和一个memset
调用,所以我得到了其中的一些部分,但对我来说还没有全部整合在一起。(附言:我对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分配堆内存,该LLVM StrctType超出了创建它的函数的寿命(如果你觉得很慷慨,以后如何释放内存)。
我评论的部分来自我最初的例子,但作为一个新手,IR的见解就更少了。。。
我对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细节了。你会在IRC频道的#llvm中得到更多的问题,或者在这里提出特定的问题,而不是试图进行反向工程。
我不建议查看Clang发射的未优化IR,因为它太冗长了。-O1
使其可读性更强——这是-O1
版本,其中注释了行(我还重新排序了两行,使其更可读):
%struct.Alloc=类型{i32};定义Alloc类型。定义noalias%struct.Alloc*@_Z8allocPtrv()#0{%1=尾调用noalias i8*@_Znwj(i32 4)#2;调用_Znwj(4)。这将重新运行i8*%3=位转换i8*%1到i32*;将返回值强制转换为i32*(int*)存储i32 0,i32*%3,align 4。。。并将其内容清零%2=位转换i8*%1到%struct.Alloc*;将返回值强制转换为Alloc*ret%struct.Alloc*%2。。。并将其退回};声明_Znwj函数。这不需要定义,因为它已经定义了;在libstdc++中:这是"operator new"。您可以通过将此字符串传递给;C++解映射器,例如http://demangler.com/.声明noalias i8*@_Znwj(i32)#1定义i32@main()#0{%1=尾部调用%struct.Alloc*@_Z8allocPtrv();调用_Z8allocPtrv(如上定义)ret i32 0}
这是一个new
调用,而不是本地分配,因此在离开@_Z8allocPtrv
时不会清除。本地分配实际上是用alloca
指令在LLVM IR中执行的,而不是new
调用。
如果您想知道new
是如何工作的,我相信它的标准实现使用malloc
,它由编译库的编译器转换为包含系统调用的函数。
- QSqlquery prepare()和bindvalue()不工作
- 导入库可以跨dll版本工作吗
- 如何将 I->getType() 作为参数传递给 llvm 中的 CreateCall?
- 以螺旋方式打印矩阵的程序.(工作不好)
- 对象指针在c++中是如何工作的
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- VSOMEIP-2个设备之间的通信(TCP/UDP)不工作
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- C++为线程工作动态地分割例程
- 将尾部调用void(i32,..)位转换为llvm::函数以获取FnAttribute
- 为什么我的 std::ref 无法按预期工作?
- 布尔比较运算符是如何在C++中工作的
- SampleConsensusPrerejective(ext.RANSAC)是如何真正工作的
- 不确定要在我的main中放入什么才能使我的代码正常工作
- 为什么std::condition_variable notify_all的工作速度比notify_one快(对于随机请
- SFINAE 不在 llvm/clang 上工作
- 错误:不是.exe已停止工作 - 尝试构建 LLVM 时
- 如何使llvm-jit在MSVC++中工作
- 了解内存分配的工作原理(LLVM)
- c++模板在GCC中工作正常,但在LLVM-GCC编译器中显示编译时错误