載入/儲存佇列(load/store queue)
亂序核心很樂意重排算術,但記憶體更棘手:一個載入可能讀取某個位置,而一個較早、尚未完成的儲存正要寫那個位置。讓該載入提早執行就會讀到過時資料、悄悄毀掉程式。想像一個收發櫃台,在把包裹交給來索取的人之前,必須先檢查是否有較早的指令正要更動那同一個包裹。載入/儲存佇列(LSQ)就是那個櫃台:它是追蹤所有在途記憶體運算、並在對同一位址的載入與儲存之間強制正確順序的硬體。
具體來說,每個載入與儲存都按程式順序進入 LSQ(通常分為載入佇列與儲存佇列)。儲存的位址與資料在那裡被緩衝,直到該儲存提交為止——它不會被推測地寫進記憶體或快取。當一個載入想執行時,硬體做記憶體消歧:它檢查儲存佇列,看有沒有對同一位址、且其值尚未抵達記憶體的較舊儲存。若找到一個資料已備妥的,就把那個儲存的值直接前送給載入(儲存到載入前送),完全跳過記憶體。若有一個較舊儲存的位址仍未知,載入就得等待,或推測執行、並準備在猜錯時重新執行。儲存本身只在提交時、按順序排入記憶體。
LSQ 讓亂序執行對記憶體(而不只對暫存器)也安全,而儲存到載入前送對常見的生產者-消費者模式是相當可觀的效能收益。誠實的難處在於:位址常常很晚才算出,所以機器必須猜一個載入與一個位址未知的儲存是否會碰撞(記憶體相依推測),並在猜錯時復原——這是核心中微妙、易出錯、且與安全相關的一塊。它也正是好幾種微架構攻擊所牽涉的那種推測式記憶體機制。
store [x1] = 5(位址 x1 仍未知),接著 load x9 = [x2]。若 x2 結果等於 x1,這個載入必須拿到 5——LSQ 會前送被緩衝的儲存值;若它先前猜兩者不同而提早執行,就必須重新執行。
追蹤在途的記憶體運算;把待決的儲存前送給相符的載入,或在猜錯時重新執行。
記憶體不能像暫存器那樣自由重排,因為儲存/載入的位址可能別名重疊、且常很晚才算出。消歧靠猜測與復原;猜錯既是正確性錯誤,歷史上也是一個推測式側通道面。