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的调用,后者从堆中分配内存。

从堆中分配的内存链接到当前栈块中的列表中,并与之一起释放。这可以防止内存泄漏。