LLVM 中的分段堆栈¶
简介¶
分段堆栈允许增量分配堆栈空间,而不是在线程初始化时分配一个整体块(具有某种最坏情况的大小)。这是通过分配堆栈块(以下称为堆栈段)并将它们链接到双向链表中来完成的。函数序言负责检查当前堆栈段是否有足够的空间供函数执行;如果空间不足,则调用 libgcc 运行时来分配更多堆栈空间。分段堆栈通过 LLVM 函数上的 "split-stack"
属性启用。
运行时功能已经存在于 libgcc 中。
实现细节¶
分配堆栈段¶
如上所述,函数序言检查当前堆栈段是否有足够的空间。当前的方法是使用 TCB 中的一个槽来存储当前的堆栈限制(减去分配新块所需的空间量) - 这个槽的偏移量再次由 libgcc
决定。在 x86-64 上的生成汇编代码如下所示
leaq -8(%rsp), %r10
cmpq %fs:112, %r10
jg .LBB0_2
# More stack space needs to be allocated
movabsq $8, %r10 # The amount of space needed
movabsq $0, %r11 # The total size of arguments passed on stack
callq __morestack
ret # The reason for this extra return is explained below
.LBB0_2:
# Usual prologue continues here
堆栈上函数参数的大小需要传递给 __morestack
(此函数在 libgcc
中实现),因为这些字节数必须从前一个堆栈段复制到当前的堆栈段。这是为了使函数参数的 SP(和 FP)相对寻址按预期工作。
不寻常的 ret
是使调用 __morestack
的函数正确返回所必需的。__morestack
没有返回,而是调用了 .LBB0_2
。这是可能的,因为 ret
指令的大小和调用 __morestack
的 PC 都是已知的。当函数体返回时,控制权会返回给 __morestack
。__morestack
然后会释放新的堆栈段,恢复正确的 SP 值,并执行第二次返回,这将控制权返回给正确的调用者。
可变大小的 Allocas¶
分配堆栈段部分自动假定每个堆栈帧的大小都是固定的。但是,LLVM 允许使用 llvm.alloca
intrinsic 在堆栈上分配动态大小的内存块。当遇到这样的可变大小的 alloca 时,会生成代码来
检查当前堆栈段是否有足够的空间。如果空间足够,就像正常情况一样,只需增加 SP。
如果空间不足,则生成对
libgcc
的调用,它从堆中分配内存。
从堆中分配的内存被链接到当前堆栈段中的列表中,并与当前堆栈段一起释放。这可以防止内存泄漏。