21

我開始在使用LLVM作爲後端的語言中添加閉包(lambdas)。我已經實現了它們,它們可以總是內聯的簡單情況,即不需要生成閉包定義本身的代碼,因爲它是在內聯的地方使用的。如何在LLVM IR中有效實施閉包?

但是,如果閉包不總是內聯(例如,它傳遞給另一個未內聯的函數),如何生成閉包的代碼。優選地,呼叫站點不應該關心他們是否通過常規功能或關閉,並將其稱爲正常功能。

我可以用綜合名稱生成一個函數,但它必須將引用環境作爲額外參數,並且該函數不能僅僅傳遞給不知道所需額外參數的另一個函數。

我想到了一種可能的解決方案,它使用LLVM的蹦牀內在函數,它從函數中「消除」單個參數,並返回一個指向蹦牀函數的指針,該函數只需要少一個參數。在這種情況下,如果爲閉包生成的函數將引用環境作爲第一個參數,我可以刪除它並返回一個函數,該函數的精確度與實際聲明的參數完全一致。這聽起來可行嗎?高效?有沒有更好的解決方案?

代碼示例:

def applyFunctionTo(value: Int, f: (Int) -> Int) = f(value) 

def main() = { 
    val m := 4; 
    val n := 5; 
    val lambda := { (x: Int) => x + m + n }; 
    applyFunctionTo(3, lambda) 
} 

現在,讓我們來想象,這將不會被內聯了def main() = 3 + 4 + 5,並applyFunctionTo將可能被單獨編譯,我們不能改變調用點存在。隨着蹦牀,我想生成的代碼會是這樣的(僞代碼表示,*表示指針):

def main$lambda$1(env: {m: Int, n: Int}*, x: Int) = x + env.m + env.n 
def main() = { 
    m = 4 
    n = 5 
    env* = allocate-space-for {Int, Int} 
    env = {m, n} 
    tramp* = create-trampoline-for(main$lambda$1*, env*) 
    return applyFunctionTo(3, tramp*) 
    // release memory for env and trampoline if the lambda didn't escape 
} 

這個問題似乎對不對?

+0

使用虛擬方法實現閉包和實現對象沒有區別。 – 2012-01-03 11:50:50

+0

可能你是對的,但是,語言不會有虛擬方法(還)。至少它會在這之前關閉和其他許多東西。我可能會以愚蠢的順序添加一些功能,因爲我主要是爲了學習目的而做的。我只希望最終有用的東西來自它。 – 2012-01-03 13:18:58

+0

我的意思是沒有理由發明任何新的閉包:你可以做同樣的事情,比如說C++編譯器已經在做。有可能它已經是最有效率的事情了。 – 2012-01-03 13:27:34

回答

7

聲音可行且高效。

另一種不需要蹦牀的方式是將閉包類型定義爲一對函數指針和指向環境的指針即棧指針。在C調用約定中,額外的參數被忽略,所以如果你提供環境作爲最後一個參數,你甚至可以使用(function_ptr,null)作爲普通函數的回調函數。

+0

現在我會嘗試使用蹦牀,但似乎將函數作爲一對指針傳遞的替代方案實際上可能更好,以防在程序中傳遞的大部分函數是實際具有自由變量或對象方法的閉包(其中'this'可以被認爲是一個自由變量)。我不確定該語言到底會如何,但我可能會考慮稍後切換到該表示。 – 2012-01-04 12:36:45

1

一個愚蠢的想法是,對於每個閉包,你生成一個線程局部結構來保存所需的數據(可能只是一個指向本地結構或幾個指針的指針)。

閉包的創建者負責設置TLS變量並「保存」它們的狀態(以允許遞歸調用)。

然後用戶通常調用該函數,執行該函數並使用environemnt。

調用之後,閉包的創建者將原始值「恢復」爲TLS變量。