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 值,并执行第二次返回,这将控制权返回给正确的调用者。
可变大小的 alloca¶
关于分配栈块的部分自动假设每个栈帧都具有固定大小。但是,LLVM 允许使用llvm.alloca
内联函数在栈上分配动态大小的内存块。当遇到这种可变大小的 alloca 时,会生成代码以
检查当前栈块是否有足够的空闲空间。如果有,就像在正常情况下一样,只需增加 SP。
如果没有,则生成对
libgcc
的调用,后者从堆中分配内存。
从堆中分配的内存链接到当前栈块中的列表中,并与之一起释放。这可以防止内存泄漏。