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 內建函數在堆疊上分配動態大小的記憶體區塊。當面對這種可變大小的 alloca 時,會產生程式碼來

  • 檢查當前堆疊片段是否有足夠的空間。如果有的話,就像正常情況一樣,只需增加 SP。

  • 如果沒有,則產生對 libgcc 的調用,它會從堆積中分配記憶體。

從堆積中分配的記憶體會連結到目前堆疊片段中的一個列表,並與其一起釋放。這可以防止記憶體洩漏。